@lydt/geogebra-studio 0.0.0-stage → 0.4.0-rc.1

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.
Files changed (183) hide show
  1. package/.agents/plugins/marketplace.json +21 -0
  2. package/.codex-plugin/plugin.json +21 -0
  3. package/.mcp.json +15 -0
  4. package/LICENSE +339 -0
  5. package/LICENSE-MIT +21 -0
  6. package/README.md +106 -2
  7. package/README.zh-CN.md +87 -0
  8. package/THIRD_PARTY_NOTICES.md +35 -0
  9. package/dist/cli.d.ts +27 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +234 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/export/files.d.ts +4 -0
  14. package/dist/export/files.d.ts.map +1 -0
  15. package/dist/export/files.js +43 -0
  16. package/dist/export/files.js.map +1 -0
  17. package/dist/export/gikz.d.ts +11 -0
  18. package/dist/export/gikz.d.ts.map +1 -0
  19. package/dist/export/gikz.js +28 -0
  20. package/dist/export/gikz.js.map +1 -0
  21. package/dist/export/html.d.ts +14 -0
  22. package/dist/export/html.d.ts.map +1 -0
  23. package/dist/export/html.js +97 -0
  24. package/dist/export/html.js.map +1 -0
  25. package/dist/export/semantic.d.ts +43 -0
  26. package/dist/export/semantic.d.ts.map +1 -0
  27. package/dist/export/semantic.js +212 -0
  28. package/dist/export/semantic.js.map +1 -0
  29. package/dist/export/viewer-themes.d.ts +76 -0
  30. package/dist/export/viewer-themes.d.ts.map +1 -0
  31. package/dist/export/viewer-themes.js +169 -0
  32. package/dist/export/viewer-themes.js.map +1 -0
  33. package/dist/gikz-browser.js +770 -0
  34. package/dist/index.d.ts +5 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +84 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/server.d.ts +35 -0
  39. package/dist/server.d.ts.map +1 -0
  40. package/dist/server.js +175 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/studio/artifact.d.ts +37 -0
  43. package/dist/studio/artifact.d.ts.map +1 -0
  44. package/dist/studio/artifact.js +61 -0
  45. package/dist/studio/artifact.js.map +1 -0
  46. package/dist/studio/browser.d.ts +7 -0
  47. package/dist/studio/browser.d.ts.map +1 -0
  48. package/dist/studio/browser.js +67 -0
  49. package/dist/studio/browser.js.map +1 -0
  50. package/dist/studio/install.d.ts +4 -0
  51. package/dist/studio/install.d.ts.map +1 -0
  52. package/dist/studio/install.js +60 -0
  53. package/dist/studio/install.js.map +1 -0
  54. package/dist/tools/apps-api-tools.d.ts +4 -0
  55. package/dist/tools/apps-api-tools.d.ts.map +1 -0
  56. package/dist/tools/apps-api-tools.js +96 -0
  57. package/dist/tools/apps-api-tools.js.map +1 -0
  58. package/dist/tools/educational-templates.d.ts +33 -0
  59. package/dist/tools/educational-templates.d.ts.map +1 -0
  60. package/dist/tools/educational-templates.js +261 -0
  61. package/dist/tools/educational-templates.js.map +1 -0
  62. package/dist/tools/export-tools.d.ts +3 -0
  63. package/dist/tools/export-tools.d.ts.map +1 -0
  64. package/dist/tools/export-tools.js +118 -0
  65. package/dist/tools/export-tools.js.map +1 -0
  66. package/dist/tools/geogebra-tools.d.ts +15 -0
  67. package/dist/tools/geogebra-tools.d.ts.map +1 -0
  68. package/dist/tools/geogebra-tools.js +3441 -0
  69. package/dist/tools/geogebra-tools.js.map +1 -0
  70. package/dist/tools/index.d.ts +43 -0
  71. package/dist/tools/index.d.ts.map +1 -0
  72. package/dist/tools/index.js +183 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/tools/performance-tools.d.ts +7 -0
  75. package/dist/tools/performance-tools.d.ts.map +1 -0
  76. package/dist/tools/performance-tools.js +312 -0
  77. package/dist/tools/performance-tools.js.map +1 -0
  78. package/dist/tools/templates/algebra-templates.d.ts +7 -0
  79. package/dist/tools/templates/algebra-templates.d.ts.map +1 -0
  80. package/dist/tools/templates/algebra-templates.js +568 -0
  81. package/dist/tools/templates/algebra-templates.js.map +1 -0
  82. package/dist/tools/templates/geometry-templates.d.ts +7 -0
  83. package/dist/tools/templates/geometry-templates.d.ts.map +1 -0
  84. package/dist/tools/templates/geometry-templates.js +492 -0
  85. package/dist/tools/templates/geometry-templates.js.map +1 -0
  86. package/dist/types/geogebra.d.ts +103 -0
  87. package/dist/types/geogebra.d.ts.map +1 -0
  88. package/dist/types/geogebra.js +34 -0
  89. package/dist/types/geogebra.js.map +1 -0
  90. package/dist/types/mcp.d.ts +125 -0
  91. package/dist/types/mcp.d.ts.map +1 -0
  92. package/dist/types/mcp.js +19 -0
  93. package/dist/types/mcp.js.map +1 -0
  94. package/dist/utils/app-info.d.ts +12 -0
  95. package/dist/utils/app-info.d.ts.map +1 -0
  96. package/dist/utils/app-info.js +30 -0
  97. package/dist/utils/app-info.js.map +1 -0
  98. package/dist/utils/errors.d.ts +37 -0
  99. package/dist/utils/errors.d.ts.map +1 -0
  100. package/dist/utils/errors.js +72 -0
  101. package/dist/utils/errors.js.map +1 -0
  102. package/dist/utils/gemini-compatibility.d.ts +39 -0
  103. package/dist/utils/gemini-compatibility.d.ts.map +1 -0
  104. package/dist/utils/gemini-compatibility.js +154 -0
  105. package/dist/utils/gemini-compatibility.js.map +1 -0
  106. package/dist/utils/geogebra-instance.d.ts +252 -0
  107. package/dist/utils/geogebra-instance.d.ts.map +1 -0
  108. package/dist/utils/geogebra-instance.js +1559 -0
  109. package/dist/utils/geogebra-instance.js.map +1 -0
  110. package/dist/utils/geogebra-mock.d.ts +172 -0
  111. package/dist/utils/geogebra-mock.d.ts.map +1 -0
  112. package/dist/utils/geogebra-mock.js +732 -0
  113. package/dist/utils/geogebra-mock.js.map +1 -0
  114. package/dist/utils/logger.d.ts +4 -0
  115. package/dist/utils/logger.d.ts.map +1 -0
  116. package/dist/utils/logger.js +46 -0
  117. package/dist/utils/logger.js.map +1 -0
  118. package/dist/utils/performance/index.d.ts +76 -0
  119. package/dist/utils/performance/index.d.ts.map +1 -0
  120. package/dist/utils/performance/index.js +163 -0
  121. package/dist/utils/performance/index.js.map +1 -0
  122. package/dist/utils/performance/instance-pool.d.ts +78 -0
  123. package/dist/utils/performance/instance-pool.d.ts.map +1 -0
  124. package/dist/utils/performance/instance-pool.js +268 -0
  125. package/dist/utils/performance/instance-pool.js.map +1 -0
  126. package/dist/utils/validation.d.ts +92 -0
  127. package/dist/utils/validation.d.ts.map +1 -0
  128. package/dist/utils/validation.js +557 -0
  129. package/dist/utils/validation.js.map +1 -0
  130. package/dist/widget.html +110 -0
  131. package/docs/APPS-API-BRIDGE.md +28 -0
  132. package/docs/MODERNIZATION.md +15 -0
  133. package/docs/TANGENT-REGRESSION.md +31 -0
  134. package/docs/VALIDATION.md +23 -0
  135. package/docs/VISUAL-STANDARD.md +26 -0
  136. package/docs/images/native-construction.png +0 -0
  137. package/examples/tangent-construction.json +26 -0
  138. package/gikz/LICENSE +339 -0
  139. package/gikz/PROVENANCE.md +8 -0
  140. package/gikz/browser.cjs +2 -0
  141. package/gikz/cli.cjs +195 -0
  142. package/gikz/core.cjs +806 -0
  143. package/mcp.json +17 -0
  144. package/package.json +106 -4
  145. package/plugin.json +35 -0
  146. package/scripts/build-plugin.cjs +15 -0
  147. package/scripts/build-widget.cjs +7 -0
  148. package/skills/geogebra-construct/SKILL.md +37 -0
  149. package/skills/geogebra-setup/SKILL.md +33 -0
  150. package/skills/geometry-export/SKILL.md +29 -0
  151. package/src/cli.ts +212 -0
  152. package/src/export/files.ts +25 -0
  153. package/src/export/gikz.ts +22 -0
  154. package/src/export/html.ts +108 -0
  155. package/src/export/semantic.ts +180 -0
  156. package/src/export/viewer-themes.ts +165 -0
  157. package/src/index.ts +71 -0
  158. package/src/server.ts +156 -0
  159. package/src/studio/artifact.ts +50 -0
  160. package/src/studio/browser.ts +38 -0
  161. package/src/studio/install.ts +43 -0
  162. package/src/tools/apps-api-tools.ts +96 -0
  163. package/src/tools/educational-templates.ts +299 -0
  164. package/src/tools/export-tools.ts +110 -0
  165. package/src/tools/geogebra-tools.ts +3835 -0
  166. package/src/tools/index.ts +192 -0
  167. package/src/tools/performance-tools.ts +322 -0
  168. package/src/tools/templates/algebra-templates.ts +619 -0
  169. package/src/tools/templates/geometry-templates.ts +550 -0
  170. package/src/types/geogebra.ts +140 -0
  171. package/src/types/global.d.ts +31 -0
  172. package/src/types/mcp.ts +141 -0
  173. package/src/utils/app-info.ts +35 -0
  174. package/src/utils/errors.ts +97 -0
  175. package/src/utils/gemini-compatibility.ts +193 -0
  176. package/src/utils/geogebra-instance.ts +1711 -0
  177. package/src/utils/geogebra-mock.ts +867 -0
  178. package/src/utils/logger.ts +60 -0
  179. package/src/utils/performance/index.ts +211 -0
  180. package/src/utils/performance/instance-pool.ts +323 -0
  181. package/src/utils/validation.ts +647 -0
  182. package/tsconfig.json +35 -0
  183. package/widget/main.ts +12 -0
@@ -0,0 +1,3441 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.geogebraTools = exports.instancePool = void 0;
7
+ // import { MockGeoGebraInstance } from '../utils/geogebra-mock'; // Mock implementation for testing
8
+ const geogebra_instance_1 = require("../utils/geogebra-instance"); // Real implementation (production)
9
+ const logger_1 = __importDefault(require("../utils/logger"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const validation_1 = require("../utils/validation");
13
+ // Global instance pool for managing GeoGebra instances
14
+ class GeoGebraInstancePool {
15
+ instances = new Map();
16
+ defaultInstance;
17
+ async getDefaultInstance() {
18
+ if (!this.defaultInstance) {
19
+ this.defaultInstance = new geogebra_instance_1.GeoGebraInstance({
20
+ appName: 'classic', // Use classic app for full functionality (from GEB-12 fixes)
21
+ width: 800,
22
+ height: 600,
23
+ showMenuBar: false,
24
+ showToolBar: false,
25
+ showAlgebraInput: false
26
+ });
27
+ try {
28
+ await this.defaultInstance.initialize(true); // Initialize in headless mode for production
29
+ logger_1.default.info('Default Real GeoGebra instance initialized');
30
+ }
31
+ catch (error) {
32
+ logger_1.default.error('Failed to initialize default GeoGebra instance', error);
33
+ this.defaultInstance = undefined;
34
+ throw error;
35
+ }
36
+ }
37
+ return this.defaultInstance;
38
+ }
39
+ async cleanup() {
40
+ if (this.defaultInstance) {
41
+ await this.defaultInstance.cleanup();
42
+ this.defaultInstance = undefined;
43
+ }
44
+ for (const instance of this.instances.values()) {
45
+ await instance.cleanup();
46
+ }
47
+ this.instances.clear();
48
+ }
49
+ }
50
+ exports.instancePool = new GeoGebraInstancePool();
51
+ const DEFAULT_LINE_THICKNESS = 2;
52
+ const DEFAULT_POINT_SIZE = 2;
53
+ const LABEL_MODE_VALUES = ['all', 'none', 'points_only', 'specified'];
54
+ function getWorkspaceDir() {
55
+ return process.env['VSCODE_WORKSPACE_FOLDER'] || process.cwd();
56
+ }
57
+ function getRequestedFilename(params) {
58
+ const filename = params['filename'];
59
+ const outputPath = params['outputPath'];
60
+ return filename || outputPath;
61
+ }
62
+ async function saveExportFile(instance, params, data, encoding) {
63
+ const requestedFilename = getRequestedFilename(params);
64
+ if (!requestedFilename) {
65
+ return undefined;
66
+ }
67
+ const workspaceDir = getWorkspaceDir();
68
+ const fullPath = instance.validateFilePath(requestedFilename, workspaceDir);
69
+ await fs_1.default.promises.mkdir(path_1.default.dirname(fullPath), { recursive: true });
70
+ if (encoding === 'base64') {
71
+ await fs_1.default.promises.writeFile(fullPath, Buffer.from(data, 'base64'));
72
+ }
73
+ else {
74
+ await fs_1.default.promises.writeFile(fullPath, data, encoding);
75
+ }
76
+ return fullPath;
77
+ }
78
+ async function applyLatexCaptionDefaults(instance, objectNames) {
79
+ if (objectNames.length === 0) {
80
+ return;
81
+ }
82
+ const captionCapableInstance = instance;
83
+ if (typeof captionCapableInstance.applyLatexCaptionDefaults !== 'function') {
84
+ return;
85
+ }
86
+ // Label visibility is controlled separately to support point-only / whitelist modes.
87
+ await captionCapableInstance.applyLatexCaptionDefaults(objectNames, false);
88
+ }
89
+ async function applyDefaultStyleSettings(instance, objectNames) {
90
+ if (objectNames.length === 0) {
91
+ return;
92
+ }
93
+ const styleCapableInstance = instance;
94
+ if (typeof styleCapableInstance.applyDefaultStyleSettings !== 'function') {
95
+ return;
96
+ }
97
+ await styleCapableInstance.applyDefaultStyleSettings(objectNames, DEFAULT_POINT_SIZE, DEFAULT_LINE_THICKNESS);
98
+ }
99
+ function normalizeVisibleLabels(value) {
100
+ if (!Array.isArray(value)) {
101
+ return [];
102
+ }
103
+ const labels = value
104
+ .filter((item) => typeof item === 'string')
105
+ .map(item => item.trim())
106
+ .filter(item => item.length > 0);
107
+ return Array.from(new Set(labels));
108
+ }
109
+ function parseLabelMode(value) {
110
+ if (typeof value !== 'string') {
111
+ return undefined;
112
+ }
113
+ const normalized = value.trim().toLowerCase();
114
+ if (LABEL_MODE_VALUES.includes(normalized)) {
115
+ return normalized;
116
+ }
117
+ return undefined;
118
+ }
119
+ async function setSingleLabelVisibility(instance, objectName, visible) {
120
+ await instance.callAppsApi('setLabelVisible', [objectName, visible]);
121
+ }
122
+ async function applyLabelVisibilityPolicy(instance, objectNames, options) {
123
+ if (objectNames.length === 0) {
124
+ return;
125
+ }
126
+ const uniqueNames = Array.from(new Set(objectNames.filter(name => name && name.trim())));
127
+ if (uniqueNames.length === 0) {
128
+ return;
129
+ }
130
+ const visibleSet = new Set(options.visibleLabels ?? []);
131
+ const failed = [];
132
+ for (const name of uniqueNames) {
133
+ try {
134
+ let visible = false;
135
+ if (options.mode === 'all') {
136
+ visible = true;
137
+ }
138
+ else if (options.mode === 'specified') {
139
+ visible = visibleSet.has(name);
140
+ }
141
+ else if (options.mode === 'points_only') {
142
+ const info = await instance.getObjectInfo(name);
143
+ visible = (info?.type?.toLowerCase() || '') === 'point';
144
+ }
145
+ else {
146
+ visible = false;
147
+ }
148
+ await setSingleLabelVisibility(instance, name, visible);
149
+ }
150
+ catch (_error) {
151
+ failed.push(name);
152
+ }
153
+ }
154
+ if (failed.length > 0) {
155
+ logger_1.default.warn(`Failed to apply label visibility for ${failed.length} object(s)`, {
156
+ failedObjects: failed,
157
+ labelMode: options.mode
158
+ });
159
+ }
160
+ }
161
+ async function applyObjectDefaults(instance, objectNames, labelOptions = { mode: 'points_only' }) {
162
+ if (objectNames.length === 0) {
163
+ return;
164
+ }
165
+ await applyLatexCaptionDefaults(instance, objectNames);
166
+ await applyDefaultStyleSettings(instance, objectNames);
167
+ await applyLabelVisibilityPolicy(instance, objectNames, labelOptions);
168
+ }
169
+ async function evalCommandWithLatexCaptionDefaults(instance, command) {
170
+ const labelCapableInstance = instance;
171
+ if (typeof labelCapableInstance.evalCommandGetLabels === 'function') {
172
+ const labels = await labelCapableInstance.evalCommandGetLabels(command);
173
+ await applyObjectDefaults(instance, labels);
174
+ return {
175
+ success: true,
176
+ labels
177
+ };
178
+ }
179
+ const fallbackResult = await instance.evalCommand(command);
180
+ if (!fallbackResult.success) {
181
+ return fallbackResult;
182
+ }
183
+ return {
184
+ ...fallbackResult,
185
+ labels: []
186
+ };
187
+ }
188
+ function buildPromptFramework(mode, task) {
189
+ const lines = [
190
+ `MODE: ${mode}`,
191
+ 'Goal: Convert a geometry problem statement into reliable GeoGebra commands.',
192
+ '',
193
+ 'Framework:',
194
+ '1) Extract entities and dependencies first (points, lines, circles, arcs, transformations).',
195
+ '2) Prefer dependency-based definitions over hard-coded coordinates when constraints are given.',
196
+ '3) Name intermediate objects explicitly (centers, radii, helper lines, helper points).',
197
+ '4) Build in small executable steps using geogebra_eval_commands.',
198
+ '5) After core geometry is valid, apply style commands (color, thickness, labels, visibility).',
199
+ '6) Export with geogebra_export_ggb.',
200
+ '',
201
+ 'Output format requirement:',
202
+ '- Return pure command list, one command per line, no markdown fences, no prose.'
203
+ ];
204
+ if (task && task.trim()) {
205
+ lines.push('', 'Task:', task.trim());
206
+ }
207
+ lines.push('', 'Quality checklist:', '- Every derived point should be defined from prior objects.', '- Keep object relationships editable (sliders/rotations/midpoints remain live).', '- Avoid breaking constraints by arbitrary absolute coordinates unless explicitly required.', '- Keep labels minimal: hide labels for objects not explicitly required by the problem/figure.');
208
+ return lines.join('\n');
209
+ }
210
+ /**
211
+ * GeoGebra MCP Tools
212
+ */
213
+ exports.geogebraTools = [
214
+ {
215
+ tool: {
216
+ name: 'geogebra_eval_command',
217
+ description: 'Execute a GeoGebra command and return the result',
218
+ inputSchema: {
219
+ type: 'object',
220
+ properties: {
221
+ command: {
222
+ type: 'string',
223
+ description: 'The GeoGebra command to execute (e.g., "A = (1, 2)", "f(x) = x^2")'
224
+ }
225
+ },
226
+ required: ['command']
227
+ }
228
+ },
229
+ handler: async (params) => {
230
+ try {
231
+ const command = params['command'];
232
+ if (typeof command !== 'string' || !command.trim())
233
+ throw new Error('command must be a nonempty string');
234
+ const instance = await exports.instancePool.getDefaultInstance();
235
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
236
+ return {
237
+ content: [{
238
+ type: 'text',
239
+ text: JSON.stringify({
240
+ success: result.success,
241
+ command,
242
+ labels: result.labels,
243
+ result: result.result,
244
+ error: result.error
245
+ }, null, 2)
246
+ }]
247
+ };
248
+ }
249
+ catch (error) {
250
+ logger_1.default.error('Failed to execute GeoGebra command', error);
251
+ return {
252
+ content: [{
253
+ type: 'text',
254
+ text: JSON.stringify({
255
+ success: false,
256
+ error: error instanceof Error ? error.message : String(error)
257
+ }, null, 2)
258
+ }],
259
+ isError: true
260
+ };
261
+ }
262
+ }
263
+ },
264
+ {
265
+ tool: {
266
+ name: 'geogebra_eval_commands',
267
+ description: 'Execute multiple GeoGebra commands sequentially in one call to reduce round-trip overhead',
268
+ inputSchema: {
269
+ type: 'object',
270
+ properties: {
271
+ commands: {
272
+ type: 'array',
273
+ items: {
274
+ type: 'string'
275
+ },
276
+ description: 'GeoGebra commands to run in order (e.g., ["A=(0,0)", "B=(4,0)", "c=Circle(A,3)"])',
277
+ minItems: 1
278
+ },
279
+ stopOnError: {
280
+ type: 'boolean',
281
+ description: 'Stop immediately when a command fails (default: true)'
282
+ }
283
+ },
284
+ required: ['commands']
285
+ }
286
+ },
287
+ handler: async (params) => {
288
+ try {
289
+ const commands = params['commands'];
290
+ const stopOnError = params['stopOnError'] !== undefined ? Boolean(params['stopOnError']) : true;
291
+ if (!Array.isArray(commands) || commands.length === 0) {
292
+ throw new Error('commands must be a non-empty string array');
293
+ }
294
+ if (commands.length > 500) {
295
+ throw new Error('Too many commands in one call. Maximum is 500');
296
+ }
297
+ const instance = await exports.instancePool.getDefaultInstance();
298
+ const results = [];
299
+ for (let i = 0; i < commands.length; i++) {
300
+ const command = commands[i];
301
+ if (typeof command !== 'string' || !command.trim()) {
302
+ results.push({
303
+ index: i,
304
+ command: String(command),
305
+ success: false,
306
+ error: 'Command must be a non-empty string'
307
+ });
308
+ if (stopOnError) {
309
+ break;
310
+ }
311
+ continue;
312
+ }
313
+ const execution = await evalCommandWithLatexCaptionDefaults(instance, command);
314
+ const commandResult = {
315
+ index: i,
316
+ command,
317
+ success: execution.success
318
+ };
319
+ if (execution.result !== undefined) {
320
+ commandResult.result = execution.result;
321
+ }
322
+ if (execution.labels !== undefined) {
323
+ commandResult.result = commandResult.result === undefined
324
+ ? { labels: execution.labels }
325
+ : { output: commandResult.result, labels: execution.labels };
326
+ }
327
+ if (execution.error !== undefined) {
328
+ commandResult.error = execution.error;
329
+ }
330
+ results.push(commandResult);
331
+ if (!execution.success && stopOnError) {
332
+ break;
333
+ }
334
+ }
335
+ const successCount = results.filter(result => result.success).length;
336
+ const failureCount = results.length - successCount;
337
+ const completedAll = results.length === commands.length;
338
+ return {
339
+ content: [{
340
+ type: 'text',
341
+ text: JSON.stringify({
342
+ success: failureCount === 0,
343
+ stopOnError,
344
+ requested: commands.length,
345
+ executed: results.length,
346
+ completedAll,
347
+ successCount,
348
+ failureCount,
349
+ results
350
+ }, null, 2)
351
+ }]
352
+ };
353
+ }
354
+ catch (error) {
355
+ logger_1.default.error('Failed to execute batch commands', error);
356
+ return {
357
+ content: [{
358
+ type: 'text',
359
+ text: JSON.stringify({
360
+ success: false,
361
+ error: error instanceof Error ? error.message : String(error)
362
+ }, null, 2)
363
+ }],
364
+ isError: true
365
+ };
366
+ }
367
+ }
368
+ },
369
+ {
370
+ tool: {
371
+ name: 'geogebra_get_prompt_framework',
372
+ description: 'Get the built-in prompt framework for clients (Codex/Claude) to generate high-quality GeoGebra commands.',
373
+ inputSchema: {
374
+ type: 'object',
375
+ properties: {
376
+ task: {
377
+ type: 'string',
378
+ description: 'Optional problem statement to inject into the framework.'
379
+ },
380
+ mode: {
381
+ type: 'string',
382
+ enum: ['2D', '3D'],
383
+ description: 'Target construction mode (default: 2D).'
384
+ }
385
+ },
386
+ required: []
387
+ }
388
+ },
389
+ handler: async (params) => {
390
+ try {
391
+ const mode = (params['mode'] || '2D');
392
+ const task = params['task'];
393
+ const frameworkPrompt = buildPromptFramework(mode, task);
394
+ return {
395
+ content: [{
396
+ type: 'text',
397
+ text: JSON.stringify({
398
+ success: true,
399
+ mode,
400
+ frameworkPrompt,
401
+ recommendedWorkflow: [
402
+ 'geogebra_clear_construction',
403
+ 'geogebra_eval_commands',
404
+ 'geogebra_export_ggb'
405
+ ],
406
+ notes: [
407
+ 'This MCP does not require an extra server-side API key for generation.',
408
+ 'Client models (Codex/Claude) should read this framework and generate command lists.'
409
+ ]
410
+ }, null, 2)
411
+ }]
412
+ };
413
+ }
414
+ catch (error) {
415
+ logger_1.default.error('Failed to return prompt framework', error);
416
+ return {
417
+ content: [{
418
+ type: 'text',
419
+ text: JSON.stringify({
420
+ success: false,
421
+ error: error instanceof Error ? error.message : String(error)
422
+ }, null, 2)
423
+ }],
424
+ isError: true
425
+ };
426
+ }
427
+ }
428
+ },
429
+ {
430
+ tool: {
431
+ name: 'geogebra_create_point',
432
+ description: 'Create a point in GeoGebra with specified coordinates',
433
+ inputSchema: {
434
+ type: 'object',
435
+ properties: {
436
+ name: {
437
+ type: 'string',
438
+ description: 'Name of the point (e.g., "A", "P1")'
439
+ },
440
+ x: {
441
+ type: 'number',
442
+ description: 'X coordinate of the point'
443
+ },
444
+ y: {
445
+ type: 'number',
446
+ description: 'Y coordinate of the point'
447
+ },
448
+ z: { type: 'number', description: 'Optional Z coordinate' }
449
+ },
450
+ required: ['name', 'x', 'y']
451
+ }
452
+ },
453
+ handler: async (params) => {
454
+ try {
455
+ const name = params['name'];
456
+ const x = params['x'];
457
+ const y = params['y'];
458
+ // Validate parameters
459
+ const nameValidation = (0, validation_1.validateObjectName)(name);
460
+ if (!nameValidation.isValid) {
461
+ throw new Error(`Invalid name: ${nameValidation.error}`);
462
+ }
463
+ const coordValidation = (0, validation_1.validateCoordinates)(x, y);
464
+ if (!coordValidation.isValid) {
465
+ throw new Error(`Invalid coordinates: ${coordValidation.error}`);
466
+ }
467
+ const z = params['z'];
468
+ if (z !== undefined && (typeof z !== 'number' || !Number.isFinite(z)))
469
+ throw new Error('Invalid z coordinate');
470
+ const command = z === undefined ? `${name} = (${x}, ${y})` : `${name} = (${x}, ${y}, ${z})`;
471
+ const instance = await exports.instancePool.getDefaultInstance();
472
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
473
+ if (result.success) {
474
+ const pointInfo = await instance.getObjectInfo(name);
475
+ return {
476
+ content: [{
477
+ type: 'text',
478
+ text: JSON.stringify({
479
+ success: true,
480
+ command,
481
+ point: pointInfo
482
+ }, null, 2)
483
+ }]
484
+ };
485
+ }
486
+ else {
487
+ throw new Error(result.error || 'Failed to create point');
488
+ }
489
+ }
490
+ catch (error) {
491
+ logger_1.default.error('Failed to create point', error);
492
+ return {
493
+ content: [{
494
+ type: 'text',
495
+ text: JSON.stringify({
496
+ success: false,
497
+ error: error instanceof Error ? error.message : String(error)
498
+ }, null, 2)
499
+ }],
500
+ isError: true
501
+ };
502
+ }
503
+ }
504
+ },
505
+ {
506
+ tool: {
507
+ name: 'geogebra_create_line',
508
+ description: 'Create a line in GeoGebra using two points or an equation. Provide either point1 and point2 parameters for two-point method, OR equation parameter for equation method.',
509
+ inputSchema: {
510
+ type: 'object',
511
+ properties: {
512
+ name: {
513
+ type: 'string',
514
+ description: 'Name of the line (e.g., "l", "line1")'
515
+ },
516
+ point1: {
517
+ type: 'string',
518
+ description: 'Name of the first point (for two-point method). Optional if using equation method.'
519
+ },
520
+ point2: {
521
+ type: 'string',
522
+ description: 'Name of the second point (for two-point method). Optional if using equation method.'
523
+ },
524
+ equation: {
525
+ type: 'string',
526
+ description: 'Linear equation (e.g., "y = 2x + 3", "x + y = 5") for equation method. Optional if using two-point method.'
527
+ }
528
+ },
529
+ required: ['name']
530
+ }
531
+ },
532
+ handler: async (params) => {
533
+ try {
534
+ const name = params['name'];
535
+ const point1 = params['point1'];
536
+ const point2 = params['point2'];
537
+ const equation = params['equation'];
538
+ // Validate line name
539
+ const nameValidation = (0, validation_1.validateObjectName)(name);
540
+ if (!nameValidation.isValid) {
541
+ throw new Error(`Invalid line name: ${nameValidation.error}`);
542
+ }
543
+ let command;
544
+ // Validate parameters based on method
545
+ if (point1 && point2) {
546
+ // Two-point method - validate point names
547
+ const point1Validation = (0, validation_1.validateObjectName)(point1);
548
+ if (!point1Validation.isValid) {
549
+ throw new Error(`Invalid first point name: ${point1Validation.error}`);
550
+ }
551
+ const point2Validation = (0, validation_1.validateObjectName)(point2);
552
+ if (!point2Validation.isValid) {
553
+ throw new Error(`Invalid second point name: ${point2Validation.error}`);
554
+ }
555
+ command = `${name} = Line(${point1}, ${point2})`;
556
+ }
557
+ else if (equation) {
558
+ // Equation method - validate equation format
559
+ const equationValidation = (0, validation_1.validateLinearEquation)(equation);
560
+ if (!equationValidation.isValid) {
561
+ throw new Error(`Invalid equation: ${equationValidation.error}`);
562
+ }
563
+ // For equation-based lines, we define them directly
564
+ command = `${name}: ${equation}`;
565
+ }
566
+ else {
567
+ throw new Error('Either provide two points or an equation for the line');
568
+ }
569
+ const instance = await exports.instancePool.getDefaultInstance();
570
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
571
+ if (result.success) {
572
+ const lineInfo = await instance.getObjectInfo(name);
573
+ return {
574
+ content: [{
575
+ type: 'text',
576
+ text: JSON.stringify({
577
+ success: true,
578
+ command,
579
+ line: lineInfo,
580
+ method: point1 && point2 ? 'two-point' : 'equation'
581
+ }, null, 2)
582
+ }]
583
+ };
584
+ }
585
+ else {
586
+ throw new Error(result.error || 'Failed to create line');
587
+ }
588
+ }
589
+ catch (error) {
590
+ logger_1.default.error('Failed to create line', error);
591
+ return {
592
+ content: [{
593
+ type: 'text',
594
+ text: JSON.stringify({
595
+ success: false,
596
+ error: error instanceof Error ? error.message : String(error)
597
+ }, null, 2)
598
+ }],
599
+ isError: true
600
+ };
601
+ }
602
+ }
603
+ },
604
+ {
605
+ tool: {
606
+ name: 'geogebra_create_circle',
607
+ description: 'Create a circle in GeoGebra with center and radius, or through three points. Provide either center and radius parameters for center-radius method, OR point1, point2, and point3 parameters for three-point method.',
608
+ inputSchema: {
609
+ type: 'object',
610
+ properties: {
611
+ name: {
612
+ type: 'string',
613
+ description: 'Name of the circle (e.g., "c", "circle1")'
614
+ },
615
+ center: {
616
+ type: 'string',
617
+ description: 'Name of the center point (for center-radius method). Optional if using three-point method.'
618
+ },
619
+ radius: {
620
+ type: 'number',
621
+ description: 'Radius of the circle (for center-radius method). Optional if using three-point method.',
622
+ minimum: 0
623
+ },
624
+ point1: {
625
+ type: 'string',
626
+ description: 'First point for three-point circle method. Optional if using center-radius method.'
627
+ },
628
+ point2: {
629
+ type: 'string',
630
+ description: 'Second point for three-point circle method. Optional if using center-radius method.'
631
+ },
632
+ point3: {
633
+ type: 'string',
634
+ description: 'Third point for three-point circle method. Optional if using center-radius method.'
635
+ }
636
+ },
637
+ required: ['name']
638
+ }
639
+ },
640
+ handler: async (params) => {
641
+ try {
642
+ const name = params['name'];
643
+ const center = params['center'];
644
+ const radius = params['radius'];
645
+ const point1 = params['point1'];
646
+ const point2 = params['point2'];
647
+ const point3 = params['point3'];
648
+ // Validate circle name
649
+ const nameValidation = (0, validation_1.validateObjectName)(name);
650
+ if (!nameValidation.isValid) {
651
+ throw new Error(`Invalid circle name: ${nameValidation.error}`);
652
+ }
653
+ let command;
654
+ // Validate parameters based on method
655
+ if (center && radius !== undefined) {
656
+ // Center-radius method
657
+ const centerValidation = (0, validation_1.validateObjectName)(center);
658
+ if (!centerValidation.isValid) {
659
+ throw new Error(`Invalid center point name: ${centerValidation.error}`);
660
+ }
661
+ const radiusValidation = (0, validation_1.validateRadius)(radius);
662
+ if (!radiusValidation.isValid) {
663
+ throw new Error(`Invalid radius: ${radiusValidation.error}`);
664
+ }
665
+ command = `${name} = Circle(${center}, ${radius})`;
666
+ }
667
+ else if (point1 && point2 && point3) {
668
+ // Three-point method - validate all point names
669
+ const points = [
670
+ { name: point1, label: 'first' },
671
+ { name: point2, label: 'second' },
672
+ { name: point3, label: 'third' }
673
+ ];
674
+ for (const point of points) {
675
+ const pointValidation = (0, validation_1.validateObjectName)(point.name);
676
+ if (!pointValidation.isValid) {
677
+ throw new Error(`Invalid ${point.label} point name: ${pointValidation.error}`);
678
+ }
679
+ }
680
+ command = `${name} = Circle(${point1}, ${point2}, ${point3})`;
681
+ }
682
+ else {
683
+ throw new Error('Either provide center and radius, or three points for the circle');
684
+ }
685
+ const instance = await exports.instancePool.getDefaultInstance();
686
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
687
+ if (result.success) {
688
+ const circleInfo = await instance.getObjectInfo(name);
689
+ return {
690
+ content: [{
691
+ type: 'text',
692
+ text: JSON.stringify({
693
+ success: true,
694
+ command,
695
+ circle: circleInfo,
696
+ method: center && radius !== undefined ? 'center-radius' : 'three-point'
697
+ }, null, 2)
698
+ }]
699
+ };
700
+ }
701
+ else {
702
+ throw new Error(result.error || 'Failed to create circle');
703
+ }
704
+ }
705
+ catch (error) {
706
+ logger_1.default.error('Failed to create circle', error);
707
+ return {
708
+ content: [{
709
+ type: 'text',
710
+ text: JSON.stringify({
711
+ success: false,
712
+ error: error instanceof Error ? error.message : String(error)
713
+ }, null, 2)
714
+ }],
715
+ isError: true
716
+ };
717
+ }
718
+ }
719
+ },
720
+ {
721
+ tool: {
722
+ name: 'geogebra_create_polygon',
723
+ description: 'Create a polygon in GeoGebra with variable number of vertices',
724
+ inputSchema: {
725
+ type: 'object',
726
+ properties: {
727
+ name: {
728
+ type: 'string',
729
+ description: 'Name of the polygon (e.g., "poly", "triangle1")'
730
+ },
731
+ vertices: {
732
+ type: 'array',
733
+ items: {
734
+ type: 'string'
735
+ },
736
+ description: 'Array of point names that form the vertices of the polygon',
737
+ minItems: 3
738
+ }
739
+ },
740
+ required: ['name', 'vertices']
741
+ }
742
+ },
743
+ handler: async (params) => {
744
+ try {
745
+ const name = params['name'];
746
+ const vertices = params['vertices'];
747
+ // Validate polygon name
748
+ const nameValidation = (0, validation_1.validateObjectName)(name);
749
+ if (!nameValidation.isValid) {
750
+ throw new Error(`Invalid polygon name: ${nameValidation.error}`);
751
+ }
752
+ // Validate vertices
753
+ const verticesValidation = (0, validation_1.validatePolygonVertices)(vertices);
754
+ if (!verticesValidation.isValid) {
755
+ throw new Error(verticesValidation.error);
756
+ }
757
+ const verticesStr = vertices.join(', ');
758
+ const command = `${name} = Polygon(${verticesStr})`;
759
+ const instance = await exports.instancePool.getDefaultInstance();
760
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
761
+ if (result.success) {
762
+ const polygonInfo = await instance.getObjectInfo(name);
763
+ return {
764
+ content: [{
765
+ type: 'text',
766
+ text: JSON.stringify({
767
+ success: true,
768
+ command,
769
+ polygon: polygonInfo,
770
+ vertexCount: vertices.length
771
+ }, null, 2)
772
+ }]
773
+ };
774
+ }
775
+ else {
776
+ throw new Error(result.error || 'Failed to create polygon');
777
+ }
778
+ }
779
+ catch (error) {
780
+ logger_1.default.error('Failed to create polygon', error);
781
+ return {
782
+ content: [{
783
+ type: 'text',
784
+ text: JSON.stringify({
785
+ success: false,
786
+ error: error instanceof Error ? error.message : String(error)
787
+ }, null, 2)
788
+ }],
789
+ isError: true
790
+ };
791
+ }
792
+ }
793
+ },
794
+ {
795
+ tool: {
796
+ name: 'geogebra_get_objects',
797
+ description: 'Get all objects in the current GeoGebra construction',
798
+ inputSchema: {
799
+ type: 'object',
800
+ properties: {
801
+ type: {
802
+ type: 'string',
803
+ description: 'Optional: filter by object type. Use "all" for all objects, or specific types like "point", "line", "circle", "polygon", "function", "conic", etc.'
804
+ }
805
+ },
806
+ required: []
807
+ }
808
+ },
809
+ handler: async (params) => {
810
+ try {
811
+ const type = params['type'];
812
+ const instance = await exports.instancePool.getDefaultInstance();
813
+ // Handle the special case where type is "all" - GeoGebra API expects undefined for all objects
814
+ const typeFilter = (type === 'all') ? undefined : type;
815
+ const objectNames = await instance.getAllObjectNames(typeFilter);
816
+ const objects = [];
817
+ for (const name of objectNames) {
818
+ const info = await instance.getObjectInfo(name);
819
+ if (info) {
820
+ objects.push(info);
821
+ }
822
+ }
823
+ return {
824
+ content: [{
825
+ type: 'text',
826
+ text: JSON.stringify({
827
+ success: true,
828
+ objectCount: objects.length,
829
+ objects
830
+ }, null, 2)
831
+ }]
832
+ };
833
+ }
834
+ catch (error) {
835
+ logger_1.default.error('Failed to get objects', error);
836
+ return {
837
+ content: [{
838
+ type: 'text',
839
+ text: JSON.stringify({
840
+ success: false,
841
+ error: error instanceof Error ? error.message : String(error)
842
+ }, null, 2)
843
+ }],
844
+ isError: true
845
+ };
846
+ }
847
+ }
848
+ },
849
+ {
850
+ tool: {
851
+ name: 'geogebra_clear_construction',
852
+ description: 'Clear all objects from the GeoGebra construction',
853
+ inputSchema: {
854
+ type: 'object',
855
+ properties: {},
856
+ required: []
857
+ }
858
+ },
859
+ handler: async (_params) => {
860
+ try {
861
+ const instance = await exports.instancePool.getDefaultInstance();
862
+ await instance.newConstruction();
863
+ return {
864
+ content: [{
865
+ type: 'text',
866
+ text: JSON.stringify({
867
+ success: true,
868
+ message: 'Construction cleared successfully'
869
+ }, null, 2)
870
+ }]
871
+ };
872
+ }
873
+ catch (error) {
874
+ logger_1.default.error('Failed to clear construction', error);
875
+ return {
876
+ content: [{
877
+ type: 'text',
878
+ text: JSON.stringify({
879
+ success: false,
880
+ error: error instanceof Error ? error.message : String(error)
881
+ }, null, 2)
882
+ }],
883
+ isError: true
884
+ };
885
+ }
886
+ }
887
+ },
888
+ {
889
+ tool: {
890
+ name: 'geogebra_instance_status',
891
+ description: 'Get the status of the GeoGebra instance',
892
+ inputSchema: {
893
+ type: 'object',
894
+ properties: {},
895
+ required: []
896
+ }
897
+ },
898
+ handler: async (_params) => {
899
+ try {
900
+ const instance = await exports.instancePool.getDefaultInstance();
901
+ const isReady = await instance.isReady();
902
+ const state = instance.getState();
903
+ return {
904
+ content: [{
905
+ type: 'text',
906
+ text: JSON.stringify({
907
+ success: true,
908
+ status: {
909
+ isReady,
910
+ instanceId: state.id,
911
+ lastActivity: state.lastActivity,
912
+ config: state.config
913
+ }
914
+ }, null, 2)
915
+ }]
916
+ };
917
+ }
918
+ catch (error) {
919
+ logger_1.default.error('Failed to get instance status', error);
920
+ return {
921
+ content: [{
922
+ type: 'text',
923
+ text: JSON.stringify({
924
+ success: false,
925
+ error: error instanceof Error ? error.message : String(error)
926
+ }, null, 2)
927
+ }],
928
+ isError: true
929
+ };
930
+ }
931
+ }
932
+ },
933
+ {
934
+ tool: {
935
+ name: 'geogebra_auto_zoom',
936
+ description: 'Automatically calculate and set the optimal view to fit all visible objects with padding',
937
+ inputSchema: {
938
+ type: 'object',
939
+ properties: {
940
+ padding: {
941
+ type: 'number',
942
+ description: 'Padding percentage around objects (default: 0.2 for 20% margin)'
943
+ },
944
+ minRange: {
945
+ type: 'number',
946
+ description: 'Minimum range for each axis (default: 2)'
947
+ }
948
+ },
949
+ required: []
950
+ }
951
+ },
952
+ handler: async (params) => {
953
+ try {
954
+ const padding = params['padding'] ?? 0.2;
955
+ const minRange = params['minRange'] ?? 2;
956
+ const instance = await exports.instancePool.getDefaultInstance();
957
+ // Get all objects
958
+ const objectNames = await instance.getAllObjectNames();
959
+ const allObjects = [];
960
+ for (const name of objectNames) {
961
+ const info = await instance.getObjectInfo(name);
962
+ if (info) {
963
+ allObjects.push(info);
964
+ }
965
+ }
966
+ // Filter visible points
967
+ const visiblePoints = allObjects.filter((obj) => obj.type === 'point' && obj.visible && obj.defined &&
968
+ typeof obj.x === 'number' && typeof obj.y === 'number');
969
+ if (visiblePoints.length === 0) {
970
+ return {
971
+ content: [{
972
+ type: 'text',
973
+ text: JSON.stringify({
974
+ success: false,
975
+ error: 'No visible points found to calculate zoom'
976
+ }, null, 2)
977
+ }],
978
+ isError: true
979
+ };
980
+ }
981
+ // Calculate bounding box
982
+ let minX = Infinity;
983
+ let maxX = -Infinity;
984
+ let minY = Infinity;
985
+ let maxY = -Infinity;
986
+ for (const point of visiblePoints) {
987
+ minX = Math.min(minX, point.x);
988
+ maxX = Math.max(maxX, point.x);
989
+ minY = Math.min(minY, point.y);
990
+ maxY = Math.max(maxY, point.y);
991
+ }
992
+ // Calculate ranges
993
+ let rangeX = maxX - minX;
994
+ let rangeY = maxY - minY;
995
+ // Ensure minimum range
996
+ if (rangeX < minRange) {
997
+ const center = (minX + maxX) / 2;
998
+ minX = center - minRange / 2;
999
+ maxX = center + minRange / 2;
1000
+ rangeX = minRange;
1001
+ }
1002
+ if (rangeY < minRange) {
1003
+ const center = (minY + maxY) / 2;
1004
+ minY = center - minRange / 2;
1005
+ maxY = center + minRange / 2;
1006
+ rangeY = minRange;
1007
+ }
1008
+ // Add padding
1009
+ const padX = rangeX * padding;
1010
+ const padY = rangeY * padding;
1011
+ const viewXMin = minX - padX;
1012
+ const viewXMax = maxX + padX;
1013
+ const viewYMin = minY - padY;
1014
+ const viewYMax = maxY + padY;
1015
+ // Set the coordinate system
1016
+ await instance.setCoordSystem(viewXMin, viewXMax, viewYMin, viewYMax);
1017
+ return {
1018
+ content: [{
1019
+ type: 'text',
1020
+ text: JSON.stringify({
1021
+ success: true,
1022
+ message: 'View automatically adjusted to fit all objects',
1023
+ pointsAnalyzed: visiblePoints.length,
1024
+ boundingBox: {
1025
+ minX,
1026
+ maxX,
1027
+ minY,
1028
+ maxY
1029
+ },
1030
+ viewRange: {
1031
+ xmin: viewXMin,
1032
+ xmax: viewXMax,
1033
+ ymin: viewYMin,
1034
+ ymax: viewYMax
1035
+ },
1036
+ padding: padding * 100 + '%'
1037
+ }, null, 2)
1038
+ }]
1039
+ };
1040
+ }
1041
+ catch (error) {
1042
+ logger_1.default.error('Failed to auto-zoom', error);
1043
+ return {
1044
+ content: [{
1045
+ type: 'text',
1046
+ text: JSON.stringify({
1047
+ success: false,
1048
+ error: error instanceof Error ? error.message : String(error)
1049
+ }, null, 2)
1050
+ }],
1051
+ isError: true
1052
+ };
1053
+ }
1054
+ }
1055
+ },
1056
+ {
1057
+ tool: {
1058
+ name: 'geogebra_export_png',
1059
+ description: 'Export the current GeoGebra construction as PNG with configurable dimensions, quality, and view settings',
1060
+ inputSchema: {
1061
+ type: 'object',
1062
+ properties: {
1063
+ filename: {
1064
+ type: 'string',
1065
+ description: 'Output filename for the PNG file (e.g., "ellipse.png"). If provided, saves to file instead of returning base64.'
1066
+ },
1067
+ outputPath: {
1068
+ type: 'string',
1069
+ description: 'Alias of filename. Relative path inside workspace (e.g., "output/ellipse.png").'
1070
+ },
1071
+ scale: {
1072
+ type: 'number',
1073
+ description: 'Scale factor for the exported image (default: 1)'
1074
+ },
1075
+ width: {
1076
+ type: 'number',
1077
+ description: 'Width of the exported image in pixels'
1078
+ },
1079
+ height: {
1080
+ type: 'number',
1081
+ description: 'Height of the exported image in pixels'
1082
+ },
1083
+ transparent: {
1084
+ type: 'boolean',
1085
+ description: 'Whether the background should be transparent (default: false)'
1086
+ },
1087
+ dpi: {
1088
+ type: 'number',
1089
+ description: 'Dots per inch for the exported image (default: 72)'
1090
+ },
1091
+ xmin: {
1092
+ type: 'number',
1093
+ description: 'Minimum x-coordinate for view range'
1094
+ },
1095
+ xmax: {
1096
+ type: 'number',
1097
+ description: 'Maximum x-coordinate for view range'
1098
+ },
1099
+ ymin: {
1100
+ type: 'number',
1101
+ description: 'Minimum y-coordinate for view range'
1102
+ },
1103
+ ymax: {
1104
+ type: 'number',
1105
+ description: 'Maximum y-coordinate for view range'
1106
+ },
1107
+ showAxes: {
1108
+ type: 'boolean',
1109
+ description: 'Whether to show coordinate axes (default: true)'
1110
+ },
1111
+ showGrid: {
1112
+ type: 'boolean',
1113
+ description: 'Whether to show coordinate grid (default: false)'
1114
+ },
1115
+ autoZoom: {
1116
+ type: 'boolean',
1117
+ description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
1118
+ },
1119
+ zoomPadding: {
1120
+ type: 'number',
1121
+ description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
1122
+ },
1123
+ minRange: {
1124
+ type: 'number',
1125
+ description: 'Minimum range for each axis when auto-zooming (default: 2)'
1126
+ },
1127
+ },
1128
+ required: []
1129
+ }
1130
+ },
1131
+ handler: async (params) => {
1132
+ try {
1133
+ // Input validation with safe defaults
1134
+ const requestedFilename = getRequestedFilename(params);
1135
+ let scale = params['scale'] || 1;
1136
+ let width = params['width'];
1137
+ let height = params['height'];
1138
+ const transparent = params['transparent'] || false;
1139
+ let dpi = params['dpi'] || 72;
1140
+ let xmin = params['xmin'];
1141
+ let xmax = params['xmax'];
1142
+ let ymin = params['ymin'];
1143
+ let ymax = params['ymax'];
1144
+ const showAxes = params['showAxes'] !== undefined ? params['showAxes'] : true;
1145
+ const showGrid = params['showGrid'] || false;
1146
+ const autoZoom = params['autoZoom'] || false;
1147
+ const zoomPadding = params['zoomPadding'] ?? 0.2;
1148
+ const minRange = params['minRange'] ?? 2;
1149
+ // Validate and clamp values for safety
1150
+ if (scale !== undefined) {
1151
+ scale = Math.max(0.1, Math.min(10, scale));
1152
+ }
1153
+ if (width !== undefined) {
1154
+ width = Math.max(100, Math.min(5000, width));
1155
+ }
1156
+ if (height !== undefined) {
1157
+ height = Math.max(100, Math.min(5000, height));
1158
+ }
1159
+ if (dpi !== undefined) {
1160
+ dpi = Math.max(72, Math.min(300, dpi));
1161
+ }
1162
+ const instance = await exports.instancePool.getDefaultInstance();
1163
+ // Auto-zoom if requested (and manual coordinates not provided)
1164
+ if (autoZoom && xmin === undefined && xmax === undefined && ymin === undefined && ymax === undefined) {
1165
+ const coords = await instance.calculateAutoZoomCoordinates(zoomPadding, minRange);
1166
+ if (coords) {
1167
+ xmin = coords.xmin;
1168
+ xmax = coords.xmax;
1169
+ ymin = coords.ymin;
1170
+ ymax = coords.ymax;
1171
+ }
1172
+ }
1173
+ // Apply view settings if specified or calculated by auto-zoom
1174
+ if (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) {
1175
+ await instance.setCoordSystem(xmin, xmax, ymin, ymax);
1176
+ }
1177
+ if (params['showAxes'] !== undefined) {
1178
+ await instance.setAxesVisible(showAxes, showAxes);
1179
+ }
1180
+ if (params['showGrid'] !== undefined) {
1181
+ await instance.setGridVisible(showGrid);
1182
+ }
1183
+ // Export with enhanced parameters
1184
+ const pngBase64 = await instance.exportPNG(scale, transparent, dpi, width, height);
1185
+ const savedPath = await saveExportFile(instance, params, pngBase64, 'base64');
1186
+ if (savedPath) {
1187
+ return {
1188
+ content: [{
1189
+ type: 'text',
1190
+ text: JSON.stringify({
1191
+ success: true,
1192
+ format: 'PNG',
1193
+ saved: true,
1194
+ filename: savedPath,
1195
+ scale,
1196
+ width,
1197
+ height,
1198
+ transparent,
1199
+ dpi,
1200
+ viewSettings: {
1201
+ coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
1202
+ showAxes,
1203
+ showGrid
1204
+ }
1205
+ }, null, 2)
1206
+ }]
1207
+ };
1208
+ }
1209
+ return {
1210
+ content: [{
1211
+ type: 'text',
1212
+ text: JSON.stringify({
1213
+ success: true,
1214
+ format: 'PNG',
1215
+ saveTarget: requestedFilename,
1216
+ scale,
1217
+ width,
1218
+ height,
1219
+ transparent,
1220
+ dpi,
1221
+ viewSettings: {
1222
+ coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
1223
+ showAxes,
1224
+ showGrid
1225
+ },
1226
+ data: pngBase64,
1227
+ encoding: 'base64'
1228
+ }, null, 2)
1229
+ }]
1230
+ };
1231
+ }
1232
+ catch (error) {
1233
+ logger_1.default.error('Failed to export PNG', error);
1234
+ return {
1235
+ content: [{
1236
+ type: 'text',
1237
+ text: JSON.stringify({
1238
+ success: false,
1239
+ error: error instanceof Error ? error.message : String(error)
1240
+ }, null, 2)
1241
+ }],
1242
+ isError: true
1243
+ };
1244
+ }
1245
+ }
1246
+ },
1247
+ {
1248
+ tool: {
1249
+ name: 'geogebra_export_svg',
1250
+ description: 'Export the current GeoGebra construction as SVG with configurable view settings',
1251
+ inputSchema: {
1252
+ type: 'object',
1253
+ properties: {
1254
+ filename: {
1255
+ type: 'string',
1256
+ description: 'Output filename for the SVG file (e.g., "diagram.svg"). If provided, saves to file instead of returning SVG data.'
1257
+ },
1258
+ outputPath: {
1259
+ type: 'string',
1260
+ description: 'Alias of filename. Relative path inside workspace (e.g., "output/diagram.svg").'
1261
+ },
1262
+ xmin: {
1263
+ type: 'number',
1264
+ description: 'Minimum x-coordinate for view range'
1265
+ },
1266
+ xmax: {
1267
+ type: 'number',
1268
+ description: 'Maximum x-coordinate for view range'
1269
+ },
1270
+ ymin: {
1271
+ type: 'number',
1272
+ description: 'Minimum y-coordinate for view range'
1273
+ },
1274
+ ymax: {
1275
+ type: 'number',
1276
+ description: 'Maximum y-coordinate for view range'
1277
+ },
1278
+ showAxes: {
1279
+ type: 'boolean',
1280
+ description: 'Whether to show coordinate axes (default: true)'
1281
+ },
1282
+ showGrid: {
1283
+ type: 'boolean',
1284
+ description: 'Whether to show coordinate grid (default: false)'
1285
+ },
1286
+ autoZoom: {
1287
+ type: 'boolean',
1288
+ description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
1289
+ },
1290
+ zoomPadding: {
1291
+ type: 'number',
1292
+ description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
1293
+ }
1294
+ },
1295
+ required: []
1296
+ }
1297
+ },
1298
+ handler: async (params) => {
1299
+ try {
1300
+ const requestedFilename = getRequestedFilename(params);
1301
+ let xmin = params['xmin'];
1302
+ let xmax = params['xmax'];
1303
+ let ymin = params['ymin'];
1304
+ let ymax = params['ymax'];
1305
+ const showAxes = params['showAxes'] !== undefined ? params['showAxes'] : true;
1306
+ const showGrid = params['showGrid'] || false;
1307
+ const autoZoom = params['autoZoom'] || false;
1308
+ const zoomPadding = params['zoomPadding'] ?? 0.2;
1309
+ const instance = await exports.instancePool.getDefaultInstance();
1310
+ // Auto-zoom if requested (and manual coordinates not provided)
1311
+ if (autoZoom && xmin === undefined && xmax === undefined && ymin === undefined && ymax === undefined) {
1312
+ const coords = await instance.calculateAutoZoomCoordinates(zoomPadding, 2);
1313
+ if (coords) {
1314
+ xmin = coords.xmin;
1315
+ xmax = coords.xmax;
1316
+ ymin = coords.ymin;
1317
+ ymax = coords.ymax;
1318
+ }
1319
+ }
1320
+ // Apply view settings if specified or calculated by auto-zoom
1321
+ if (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) {
1322
+ await instance.setCoordSystem(xmin, xmax, ymin, ymax);
1323
+ }
1324
+ if (params['showAxes'] !== undefined) {
1325
+ await instance.setAxesVisible(showAxes, showAxes);
1326
+ }
1327
+ if (params['showGrid'] !== undefined) {
1328
+ await instance.setGridVisible(showGrid);
1329
+ }
1330
+ const svg = await instance.exportSVG();
1331
+ const savedPath = await saveExportFile(instance, params, svg, 'utf8');
1332
+ if (savedPath) {
1333
+ return {
1334
+ content: [{
1335
+ type: 'text',
1336
+ text: JSON.stringify({
1337
+ success: true,
1338
+ format: 'SVG',
1339
+ saved: true,
1340
+ filename: savedPath,
1341
+ viewSettings: {
1342
+ coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
1343
+ showAxes,
1344
+ showGrid
1345
+ }
1346
+ }, null, 2)
1347
+ }]
1348
+ };
1349
+ }
1350
+ return {
1351
+ content: [{
1352
+ type: 'text',
1353
+ text: JSON.stringify({
1354
+ success: true,
1355
+ format: 'SVG',
1356
+ saveTarget: requestedFilename,
1357
+ viewSettings: {
1358
+ coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
1359
+ showAxes,
1360
+ showGrid
1361
+ },
1362
+ data: svg,
1363
+ encoding: 'utf8'
1364
+ }, null, 2)
1365
+ }]
1366
+ };
1367
+ }
1368
+ catch (error) {
1369
+ logger_1.default.error('Failed to export SVG', error);
1370
+ return {
1371
+ content: [{
1372
+ type: 'text',
1373
+ text: JSON.stringify({
1374
+ success: false,
1375
+ error: error instanceof Error ? error.message : String(error)
1376
+ }, null, 2)
1377
+ }],
1378
+ isError: true
1379
+ };
1380
+ }
1381
+ }
1382
+ },
1383
+ {
1384
+ tool: {
1385
+ name: 'geogebra_export_pdf',
1386
+ description: 'Export the current GeoGebra construction as PDF with optional direct file saving',
1387
+ inputSchema: {
1388
+ type: 'object',
1389
+ properties: {
1390
+ filename: {
1391
+ type: 'string',
1392
+ description: 'Output filename for the PDF file (e.g., "diagram.pdf"). If provided, saves to file instead of returning base64.'
1393
+ },
1394
+ outputPath: {
1395
+ type: 'string',
1396
+ description: 'Alias of filename. Relative path inside workspace (e.g., "output/diagram.pdf").'
1397
+ },
1398
+ autoZoom: {
1399
+ type: 'boolean',
1400
+ description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
1401
+ },
1402
+ zoomPadding: {
1403
+ type: 'number',
1404
+ description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
1405
+ },
1406
+ minRange: {
1407
+ type: 'number',
1408
+ description: 'Minimum range for each axis when auto-zooming (default: 2)'
1409
+ },
1410
+ },
1411
+ required: []
1412
+ }
1413
+ },
1414
+ handler: async (params) => {
1415
+ try {
1416
+ const requestedFilename = getRequestedFilename(params);
1417
+ const autoZoom = params['autoZoom'] || false;
1418
+ const zoomPadding = params['zoomPadding'] ?? 0.2;
1419
+ const minRange = params['minRange'] ?? 2;
1420
+ const instance = await exports.instancePool.getDefaultInstance();
1421
+ // Auto-zoom if requested
1422
+ if (autoZoom) {
1423
+ await instance.applyAutoZoom(zoomPadding, minRange);
1424
+ }
1425
+ const pdfBase64 = await instance.exportPDF();
1426
+ const savedPath = await saveExportFile(instance, params, pdfBase64, 'base64');
1427
+ if (savedPath) {
1428
+ return {
1429
+ content: [{
1430
+ type: 'text',
1431
+ text: JSON.stringify({
1432
+ success: true,
1433
+ format: 'PDF',
1434
+ saved: true,
1435
+ filename: savedPath
1436
+ }, null, 2)
1437
+ }]
1438
+ };
1439
+ }
1440
+ return {
1441
+ content: [{
1442
+ type: 'text',
1443
+ text: JSON.stringify({
1444
+ success: true,
1445
+ format: 'PDF',
1446
+ saveTarget: requestedFilename,
1447
+ data: pdfBase64,
1448
+ encoding: 'base64'
1449
+ }, null, 2)
1450
+ }]
1451
+ };
1452
+ }
1453
+ catch (error) {
1454
+ logger_1.default.error('Failed to export PDF', error);
1455
+ return {
1456
+ content: [{
1457
+ type: 'text',
1458
+ text: JSON.stringify({
1459
+ success: false,
1460
+ error: error instanceof Error ? error.message : String(error)
1461
+ }, null, 2)
1462
+ }],
1463
+ isError: true
1464
+ };
1465
+ }
1466
+ }
1467
+ },
1468
+ {
1469
+ tool: {
1470
+ name: 'geogebra_export_ggb',
1471
+ description: 'Export the current GeoGebra construction as a .ggb file with optional direct file saving',
1472
+ inputSchema: {
1473
+ type: 'object',
1474
+ properties: {
1475
+ filename: {
1476
+ type: 'string',
1477
+ description: 'Output filename for the GGB file (e.g., "construction.ggb"). If provided, saves to file instead of returning base64.'
1478
+ },
1479
+ outputPath: {
1480
+ type: 'string',
1481
+ description: 'Alias of filename. Relative path inside workspace (e.g., "output/construction.ggb").'
1482
+ },
1483
+ autoZoom: {
1484
+ type: 'boolean',
1485
+ description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
1486
+ },
1487
+ zoomPadding: {
1488
+ type: 'number',
1489
+ description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
1490
+ },
1491
+ minRange: {
1492
+ type: 'number',
1493
+ description: 'Minimum range for each axis when auto-zooming (default: 2)'
1494
+ },
1495
+ labelMode: {
1496
+ type: 'string',
1497
+ enum: ['all', 'none', 'points_only', 'specified'],
1498
+ description: 'Label visibility mode before export (default: points_only).'
1499
+ },
1500
+ visibleLabels: {
1501
+ type: 'array',
1502
+ items: {
1503
+ type: 'string'
1504
+ },
1505
+ description: 'Explicit label whitelist. If provided, only these labels are shown (equivalent to labelMode=specified).'
1506
+ },
1507
+ captionOverrides: {
1508
+ type: 'object',
1509
+ description: 'Optional captions keyed by object name. When supplied, unspecified objects use their names as labels.',
1510
+ additionalProperties: { type: 'string' }
1511
+ }
1512
+ },
1513
+ required: []
1514
+ }
1515
+ },
1516
+ handler: async (params) => {
1517
+ try {
1518
+ const requestedFilename = getRequestedFilename(params);
1519
+ const autoZoom = params['autoZoom'] || false;
1520
+ const zoomPadding = params['zoomPadding'] ?? 0.2;
1521
+ const minRange = params['minRange'] ?? 2;
1522
+ const visibleLabels = normalizeVisibleLabels(params['visibleLabels']);
1523
+ const parsedLabelMode = parseLabelMode(params['labelMode']);
1524
+ const requestedLabelModeRaw = params['labelMode'];
1525
+ if (requestedLabelModeRaw !== undefined && !parsedLabelMode) {
1526
+ throw new Error(`Invalid labelMode. Allowed values: ${LABEL_MODE_VALUES.join(', ')}`);
1527
+ }
1528
+ const labelOptions = visibleLabels.length > 0
1529
+ ? { mode: 'specified', visibleLabels }
1530
+ : { mode: parsedLabelMode ?? 'points_only' };
1531
+ const captionOverrides = (params['captionOverrides'] ?? {});
1532
+ const instance = await exports.instancePool.getDefaultInstance();
1533
+ const allObjectNames = await instance.getAllObjectNames();
1534
+ await applyObjectDefaults(instance, allObjectNames, labelOptions);
1535
+ if (Object.keys(captionOverrides).length > 0) {
1536
+ for (const name of allObjectNames) {
1537
+ const hasCaption = Object.prototype.hasOwnProperty.call(captionOverrides, name);
1538
+ await instance.callAppsApi('setCaption', [name, hasCaption ? captionOverrides[name] : '']);
1539
+ await instance.callAppsApi('setLabelStyle', [name, hasCaption ? 3 : 0]);
1540
+ }
1541
+ }
1542
+ // Auto-zoom if requested
1543
+ if (autoZoom) {
1544
+ await instance.applyAutoZoom(zoomPadding, minRange);
1545
+ }
1546
+ const ggbBase64 = await instance.exportGGB();
1547
+ const savedPath = await saveExportFile(instance, params, ggbBase64, 'base64');
1548
+ if (savedPath) {
1549
+ return {
1550
+ content: [{
1551
+ type: 'text',
1552
+ text: JSON.stringify({
1553
+ success: true,
1554
+ format: 'GGB',
1555
+ saved: true,
1556
+ filename: savedPath,
1557
+ labelMode: labelOptions.mode,
1558
+ visibleLabels: labelOptions.visibleLabels
1559
+ }, null, 2)
1560
+ }]
1561
+ };
1562
+ }
1563
+ return {
1564
+ content: [{
1565
+ type: 'text',
1566
+ text: JSON.stringify({
1567
+ success: true,
1568
+ format: 'GGB',
1569
+ saveTarget: requestedFilename,
1570
+ labelMode: labelOptions.mode,
1571
+ visibleLabels: labelOptions.visibleLabels,
1572
+ data: ggbBase64,
1573
+ encoding: 'base64'
1574
+ }, null, 2)
1575
+ }]
1576
+ };
1577
+ }
1578
+ catch (error) {
1579
+ logger_1.default.error('Failed to export GGB', error);
1580
+ return {
1581
+ content: [{
1582
+ type: 'text',
1583
+ text: JSON.stringify({
1584
+ success: false,
1585
+ error: error instanceof Error ? error.message : String(error)
1586
+ }, null, 2)
1587
+ }],
1588
+ isError: true
1589
+ };
1590
+ }
1591
+ }
1592
+ },
1593
+ // Load GGB Tool
1594
+ {
1595
+ tool: {
1596
+ name: 'geogebra_load_ggb',
1597
+ description: 'Load a GeoGebra construction from a .ggb file or base64 data',
1598
+ inputSchema: {
1599
+ type: 'object',
1600
+ properties: {
1601
+ source: {
1602
+ type: 'string',
1603
+ description: 'Either a file path to a .ggb file (e.g., "construction.ggb") or base64-encoded .ggb data'
1604
+ }
1605
+ },
1606
+ required: ['source']
1607
+ }
1608
+ },
1609
+ handler: async (params) => {
1610
+ try {
1611
+ const source = params['source'];
1612
+ if (!source) {
1613
+ throw new Error('source parameter is required');
1614
+ }
1615
+ const instance = await exports.instancePool.getDefaultInstance();
1616
+ // Load the GGB file
1617
+ await instance.loadGGB(source);
1618
+ // Get all objects to confirm load
1619
+ const objectNames = await instance.getAllObjectNames();
1620
+ return {
1621
+ content: [{
1622
+ type: 'text',
1623
+ text: JSON.stringify({
1624
+ success: true,
1625
+ message: 'GGB file loaded successfully',
1626
+ objectCount: objectNames.length,
1627
+ objects: objectNames
1628
+ }, null, 2)
1629
+ }]
1630
+ };
1631
+ }
1632
+ catch (error) {
1633
+ logger_1.default.error('Failed to load GGB', error);
1634
+ return {
1635
+ content: [{
1636
+ type: 'text',
1637
+ text: JSON.stringify({
1638
+ success: false,
1639
+ error: error instanceof Error ? error.message : String(error)
1640
+ }, null, 2)
1641
+ }],
1642
+ isError: true
1643
+ };
1644
+ }
1645
+ }
1646
+ },
1647
+ {
1648
+ tool: {
1649
+ name: 'geogebra_plot_function',
1650
+ description: 'Plot a mathematical function f(x) = expression with configurable domain and styling',
1651
+ inputSchema: {
1652
+ type: 'object',
1653
+ properties: {
1654
+ name: {
1655
+ type: 'string',
1656
+ description: 'Name of the function (e.g., "f", "g", "func1")'
1657
+ },
1658
+ expression: {
1659
+ type: 'string',
1660
+ description: 'Mathematical expression in terms of x (e.g., "x^2", "sin(x)", "2*x + 3")'
1661
+ },
1662
+ xMin: {
1663
+ type: 'number',
1664
+ description: 'Minimum x value for the domain (optional)'
1665
+ },
1666
+ xMax: {
1667
+ type: 'number',
1668
+ description: 'Maximum x value for the domain (optional)'
1669
+ },
1670
+ color: {
1671
+ type: 'string',
1672
+ description: 'Color of the function graph (hex, rgb, or color name)'
1673
+ },
1674
+ thickness: {
1675
+ type: 'number',
1676
+ description: 'Line thickness (1-10)',
1677
+ minimum: 1,
1678
+ maximum: 10
1679
+ },
1680
+ style: {
1681
+ type: 'string',
1682
+ description: 'Line style',
1683
+ enum: ['solid', 'dashed', 'dotted']
1684
+ }
1685
+ },
1686
+ required: ['name', 'expression']
1687
+ }
1688
+ },
1689
+ handler: async (params) => {
1690
+ try {
1691
+ const name = params['name'];
1692
+ const expression = params['expression'];
1693
+ const xMin = params['xMin'];
1694
+ const xMax = params['xMax'];
1695
+ const color = params['color'];
1696
+ const thickness = params['thickness'];
1697
+ const style = params['style'];
1698
+ // Validate function name
1699
+ const nameValidation = (0, validation_1.validateObjectName)(name);
1700
+ if (!nameValidation.isValid) {
1701
+ throw new Error(`Invalid function name: ${nameValidation.error}`);
1702
+ }
1703
+ // Validate function expression
1704
+ const exprValidation = (0, validation_1.validateFunctionExpression)(expression);
1705
+ if (!exprValidation.isValid) {
1706
+ throw new Error(`Invalid expression: ${exprValidation.error}`);
1707
+ }
1708
+ // Validate domain if provided
1709
+ if (xMin !== undefined && xMax !== undefined) {
1710
+ const domainValidation = (0, validation_1.validateDomainRange)(xMin, xMax, 'x');
1711
+ if (!domainValidation.isValid) {
1712
+ throw new Error(`Invalid domain: ${domainValidation.error}`);
1713
+ }
1714
+ }
1715
+ // Validate styling if provided
1716
+ const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
1717
+ if (!styleValidation.isValid) {
1718
+ throw new Error(`Invalid styling: ${styleValidation.error}`);
1719
+ }
1720
+ const instance = await exports.instancePool.getDefaultInstance();
1721
+ // Create the function
1722
+ let command;
1723
+ if (xMin !== undefined && xMax !== undefined) {
1724
+ // Function with domain restriction
1725
+ command = `${name}(x) = If(${xMin} <= x <= ${xMax}, ${expression}, ?)`;
1726
+ }
1727
+ else {
1728
+ // Standard function
1729
+ command = `${name}(x) = ${expression}`;
1730
+ }
1731
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
1732
+ if (!result.success) {
1733
+ throw new Error(result.error || 'Failed to create function');
1734
+ }
1735
+ // Apply styling if provided
1736
+ const styleCommands = [];
1737
+ if (color) {
1738
+ styleCommands.push(`SetColor(${name}, "${color}")`);
1739
+ }
1740
+ if (thickness) {
1741
+ styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
1742
+ }
1743
+ if (style && style !== 'solid') {
1744
+ const lineType = style === 'dashed' ? '10' : '20'; // GeoGebra line types
1745
+ styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
1746
+ }
1747
+ // Execute styling commands
1748
+ for (const styleCmd of styleCommands) {
1749
+ await instance.evalCommand(styleCmd);
1750
+ }
1751
+ const functionInfo = await instance.getObjectInfo(name);
1752
+ return {
1753
+ content: [{
1754
+ type: 'text',
1755
+ text: JSON.stringify({
1756
+ success: true,
1757
+ command,
1758
+ function: functionInfo,
1759
+ domain: (xMin !== undefined && xMax !== undefined) ? { xMin, xMax } : undefined,
1760
+ styling: {
1761
+ color,
1762
+ thickness,
1763
+ style
1764
+ }
1765
+ }, null, 2)
1766
+ }]
1767
+ };
1768
+ }
1769
+ catch (error) {
1770
+ logger_1.default.error('Failed to plot function', error);
1771
+ return {
1772
+ content: [{
1773
+ type: 'text',
1774
+ text: JSON.stringify({
1775
+ success: false,
1776
+ error: error instanceof Error ? error.message : String(error)
1777
+ }, null, 2)
1778
+ }],
1779
+ isError: true
1780
+ };
1781
+ }
1782
+ }
1783
+ },
1784
+ {
1785
+ tool: {
1786
+ name: 'geogebra_plot_parametric',
1787
+ description: 'Plot a parametric curve defined by x(t) and y(t) with configurable parameter range and styling',
1788
+ inputSchema: {
1789
+ type: 'object',
1790
+ properties: {
1791
+ name: {
1792
+ type: 'string',
1793
+ description: 'Name of the parametric curve (e.g., "curve1", "c")'
1794
+ },
1795
+ xExpression: {
1796
+ type: 'string',
1797
+ description: 'X-coordinate expression in terms of parameter (e.g., "cos(t)", "t")'
1798
+ },
1799
+ yExpression: {
1800
+ type: 'string',
1801
+ description: 'Y-coordinate expression in terms of parameter (e.g., "sin(t)", "t^2")'
1802
+ },
1803
+ parameter: {
1804
+ type: 'string',
1805
+ description: 'Parameter variable name (default: "t")',
1806
+ default: 't'
1807
+ },
1808
+ tMin: {
1809
+ type: 'number',
1810
+ description: 'Minimum parameter value'
1811
+ },
1812
+ tMax: {
1813
+ type: 'number',
1814
+ description: 'Maximum parameter value'
1815
+ },
1816
+ color: {
1817
+ type: 'string',
1818
+ description: 'Color of the curve (hex, rgb, or color name)'
1819
+ },
1820
+ thickness: {
1821
+ type: 'number',
1822
+ description: 'Line thickness (1-10)',
1823
+ minimum: 1,
1824
+ maximum: 10
1825
+ },
1826
+ style: {
1827
+ type: 'string',
1828
+ description: 'Line style',
1829
+ enum: ['solid', 'dashed', 'dotted']
1830
+ }
1831
+ },
1832
+ required: ['name', 'xExpression', 'yExpression', 'tMin', 'tMax']
1833
+ }
1834
+ },
1835
+ handler: async (params) => {
1836
+ try {
1837
+ const name = params['name'];
1838
+ const xExpression = params['xExpression'];
1839
+ const yExpression = params['yExpression'];
1840
+ const parameter = params['parameter'] || 't';
1841
+ const tMin = params['tMin'];
1842
+ const tMax = params['tMax'];
1843
+ const color = params['color'];
1844
+ const thickness = params['thickness'];
1845
+ const style = params['style'];
1846
+ // Validate curve name
1847
+ const nameValidation = (0, validation_1.validateObjectName)(name);
1848
+ if (!nameValidation.isValid) {
1849
+ throw new Error(`Invalid curve name: ${nameValidation.error}`);
1850
+ }
1851
+ // Validate parametric expressions
1852
+ const exprValidation = (0, validation_1.validateParametricExpressions)(xExpression, yExpression, parameter);
1853
+ if (!exprValidation.isValid) {
1854
+ throw new Error(`Invalid expressions: ${exprValidation.error}`);
1855
+ }
1856
+ // Validate parameter range
1857
+ const rangeValidation = (0, validation_1.validateDomainRange)(tMin, tMax, parameter);
1858
+ if (!rangeValidation.isValid) {
1859
+ throw new Error(`Invalid parameter range: ${rangeValidation.error}`);
1860
+ }
1861
+ // Validate styling if provided
1862
+ const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
1863
+ if (!styleValidation.isValid) {
1864
+ throw new Error(`Invalid styling: ${styleValidation.error}`);
1865
+ }
1866
+ const instance = await exports.instancePool.getDefaultInstance();
1867
+ // Create the parametric curve using GeoGebra's Curve command
1868
+ const command = `${name} = Curve(${xExpression}, ${yExpression}, ${parameter}, ${tMin}, ${tMax})`;
1869
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
1870
+ if (!result.success) {
1871
+ throw new Error(result.error || 'Failed to create parametric curve');
1872
+ }
1873
+ // Apply styling if provided
1874
+ const styleCommands = [];
1875
+ if (color) {
1876
+ styleCommands.push(`SetColor(${name}, "${color}")`);
1877
+ }
1878
+ if (thickness) {
1879
+ styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
1880
+ }
1881
+ if (style && style !== 'solid') {
1882
+ const lineType = style === 'dashed' ? '10' : '20';
1883
+ styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
1884
+ }
1885
+ // Execute styling commands
1886
+ for (const styleCmd of styleCommands) {
1887
+ await instance.evalCommand(styleCmd);
1888
+ }
1889
+ const curveInfo = await instance.getObjectInfo(name);
1890
+ return {
1891
+ content: [{
1892
+ type: 'text',
1893
+ text: JSON.stringify({
1894
+ success: true,
1895
+ command,
1896
+ curve: curveInfo,
1897
+ parametric: {
1898
+ xExpression,
1899
+ yExpression,
1900
+ parameter,
1901
+ range: { tMin, tMax }
1902
+ },
1903
+ styling: {
1904
+ color,
1905
+ thickness,
1906
+ style
1907
+ }
1908
+ }, null, 2)
1909
+ }]
1910
+ };
1911
+ }
1912
+ catch (error) {
1913
+ logger_1.default.error('Failed to plot parametric curve', error);
1914
+ return {
1915
+ content: [{
1916
+ type: 'text',
1917
+ text: JSON.stringify({
1918
+ success: false,
1919
+ error: error instanceof Error ? error.message : String(error)
1920
+ }, null, 2)
1921
+ }],
1922
+ isError: true
1923
+ };
1924
+ }
1925
+ }
1926
+ },
1927
+ {
1928
+ tool: {
1929
+ name: 'geogebra_plot_implicit',
1930
+ description: 'Plot an implicit curve defined by F(x,y) = 0 with configurable styling',
1931
+ inputSchema: {
1932
+ type: 'object',
1933
+ properties: {
1934
+ name: {
1935
+ type: 'string',
1936
+ description: 'Name of the implicit curve (e.g., "implicit1", "curve")'
1937
+ },
1938
+ expression: {
1939
+ type: 'string',
1940
+ description: 'Implicit expression in terms of x and y (e.g., "x^2 + y^2 - 4", "x^2/4 + y^2/9 - 1")'
1941
+ },
1942
+ color: {
1943
+ type: 'string',
1944
+ description: 'Color of the curve (hex, rgb, or color name)'
1945
+ },
1946
+ thickness: {
1947
+ type: 'number',
1948
+ description: 'Line thickness (1-10)',
1949
+ minimum: 1,
1950
+ maximum: 10
1951
+ },
1952
+ style: {
1953
+ type: 'string',
1954
+ description: 'Line style',
1955
+ enum: ['solid', 'dashed', 'dotted']
1956
+ }
1957
+ },
1958
+ required: ['name', 'expression']
1959
+ }
1960
+ },
1961
+ handler: async (params) => {
1962
+ try {
1963
+ const name = params['name'];
1964
+ const expression = params['expression'];
1965
+ const color = params['color'];
1966
+ const thickness = params['thickness'];
1967
+ const style = params['style'];
1968
+ // Validate curve name
1969
+ const nameValidation = (0, validation_1.validateObjectName)(name);
1970
+ if (!nameValidation.isValid) {
1971
+ throw new Error(`Invalid curve name: ${nameValidation.error}`);
1972
+ }
1973
+ // Validate implicit expression
1974
+ const exprValidation = (0, validation_1.validateImplicitExpression)(expression);
1975
+ if (!exprValidation.isValid) {
1976
+ throw new Error(`Invalid expression: ${exprValidation.error}`);
1977
+ }
1978
+ // Validate styling if provided
1979
+ const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
1980
+ if (!styleValidation.isValid) {
1981
+ throw new Error(`Invalid styling: ${styleValidation.error}`);
1982
+ }
1983
+ const instance = await exports.instancePool.getDefaultInstance();
1984
+ // Create the implicit curve using GeoGebra's ImplicitCurve command
1985
+ const command = `${name} = ImplicitCurve(${expression})`;
1986
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
1987
+ if (!result.success) {
1988
+ throw new Error(result.error || 'Failed to create implicit curve');
1989
+ }
1990
+ // Apply styling if provided
1991
+ const styleCommands = [];
1992
+ if (color) {
1993
+ styleCommands.push(`SetColor(${name}, "${color}")`);
1994
+ }
1995
+ if (thickness) {
1996
+ styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
1997
+ }
1998
+ if (style && style !== 'solid') {
1999
+ const lineType = style === 'dashed' ? '10' : '20';
2000
+ styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
2001
+ }
2002
+ // Execute styling commands
2003
+ for (const styleCmd of styleCommands) {
2004
+ await instance.evalCommand(styleCmd);
2005
+ }
2006
+ const curveInfo = await instance.getObjectInfo(name);
2007
+ return {
2008
+ content: [{
2009
+ type: 'text',
2010
+ text: JSON.stringify({
2011
+ success: true,
2012
+ command,
2013
+ curve: curveInfo,
2014
+ implicit: {
2015
+ expression
2016
+ },
2017
+ styling: {
2018
+ color,
2019
+ thickness,
2020
+ style
2021
+ }
2022
+ }, null, 2)
2023
+ }]
2024
+ };
2025
+ }
2026
+ catch (error) {
2027
+ logger_1.default.error('Failed to plot implicit curve', error);
2028
+ return {
2029
+ content: [{
2030
+ type: 'text',
2031
+ text: JSON.stringify({
2032
+ success: false,
2033
+ error: error instanceof Error ? error.message : String(error)
2034
+ }, null, 2)
2035
+ }],
2036
+ isError: true
2037
+ };
2038
+ }
2039
+ }
2040
+ },
2041
+ // CAS (Computer Algebra System) Tools
2042
+ {
2043
+ tool: {
2044
+ name: 'geogebra_solve_equation',
2045
+ description: 'Solve algebraic equations using GeoGebra\'s CAS',
2046
+ inputSchema: {
2047
+ type: 'object',
2048
+ properties: {
2049
+ equation: {
2050
+ type: 'string',
2051
+ description: 'The equation to solve (e.g., "x^2 - 4 = 0", "2x + 3 = 7")'
2052
+ },
2053
+ variable: {
2054
+ type: 'string',
2055
+ description: 'Variable to solve for (optional, defaults to automatic detection)'
2056
+ }
2057
+ },
2058
+ required: ['equation']
2059
+ }
2060
+ },
2061
+ handler: async (params) => {
2062
+ try {
2063
+ const equation = params['equation'];
2064
+ const variable = params['variable'];
2065
+ // Validate equation
2066
+ const equationValidation = (0, validation_1.validateEquation)(equation);
2067
+ if (!equationValidation.isValid) {
2068
+ throw new Error(`Invalid equation: ${equationValidation.error}`);
2069
+ }
2070
+ // Validate variable if provided
2071
+ if (variable) {
2072
+ const variableValidation = (0, validation_1.validateVariableName)(variable);
2073
+ if (!variableValidation.isValid) {
2074
+ throw new Error(`Invalid variable: ${variableValidation.error}`);
2075
+ }
2076
+ }
2077
+ const instance = await exports.instancePool.getDefaultInstance();
2078
+ // Create the solve command
2079
+ const command = variable ?
2080
+ `Solve(${equation}, ${variable})` :
2081
+ `Solve(${equation})`;
2082
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
2083
+ if (!result.success) {
2084
+ throw new Error(result.error || 'Failed to solve equation');
2085
+ }
2086
+ return {
2087
+ content: [{
2088
+ type: 'text',
2089
+ text: JSON.stringify({
2090
+ success: true,
2091
+ equation,
2092
+ variable: variable || 'auto-detected',
2093
+ command,
2094
+ solution: result.result
2095
+ }, null, 2)
2096
+ }]
2097
+ };
2098
+ }
2099
+ catch (error) {
2100
+ logger_1.default.error('Failed to solve equation', error);
2101
+ return {
2102
+ content: [{
2103
+ type: 'text',
2104
+ text: JSON.stringify({
2105
+ success: false,
2106
+ error: error instanceof Error ? error.message : String(error)
2107
+ }, null, 2)
2108
+ }],
2109
+ isError: true
2110
+ };
2111
+ }
2112
+ }
2113
+ },
2114
+ {
2115
+ tool: {
2116
+ name: 'geogebra_solve_system',
2117
+ description: 'Solve a system of equations using GeoGebra\'s CAS via algebraic substitution',
2118
+ inputSchema: {
2119
+ type: 'object',
2120
+ properties: {
2121
+ equations: {
2122
+ type: 'array',
2123
+ items: {
2124
+ type: 'string'
2125
+ },
2126
+ description: 'Array of equations to solve (e.g., ["x + y = 5", "x - y = 1"])'
2127
+ },
2128
+ variables: {
2129
+ type: 'array',
2130
+ items: {
2131
+ type: 'string'
2132
+ },
2133
+ description: 'Variables to solve for (e.g., ["x", "y"])'
2134
+ }
2135
+ },
2136
+ required: ['equations', 'variables']
2137
+ }
2138
+ },
2139
+ handler: async (params) => {
2140
+ try {
2141
+ const equations = params['equations'];
2142
+ const variables = params['variables'];
2143
+ // Validate equations
2144
+ const equationsValidation = (0, validation_1.validateSystemOfEquations)(equations);
2145
+ if (!equationsValidation.isValid) {
2146
+ throw new Error(`Invalid equations: ${equationsValidation.error}`);
2147
+ }
2148
+ // Validate variables
2149
+ const variablesValidation = (0, validation_1.validateVariablesList)(variables);
2150
+ if (!variablesValidation.isValid) {
2151
+ throw new Error(`Invalid variables: ${variablesValidation.error}`);
2152
+ }
2153
+ const instance = await exports.instancePool.getDefaultInstance();
2154
+ // GEB-20 FIX: Implement algebraic substitution approach since Solve({equations}, {variables})
2155
+ // syntax doesn't work in this GeoGebra version
2156
+ if (equations.length === 2 && variables.length === 2) {
2157
+ // Handle 2x2 system using substitution method
2158
+ const [eq1, eq2] = equations;
2159
+ const [var1, var2] = variables;
2160
+ // Ensure we have valid equations and variables
2161
+ if (!eq1 || !eq2 || !var1 || !var2) {
2162
+ throw new Error('Invalid equations or variables provided');
2163
+ }
2164
+ // Parse equations to extract coefficients for linear systems
2165
+ // For now, implement a specific case for linear systems like:
2166
+ // x + y = 5, x - y = 1
2167
+ try {
2168
+ // Method 1: Try direct solve of individual equations
2169
+ // Convert equations to proper format and solve using simple Solve() command
2170
+ const cleanEq1 = eq1.trim();
2171
+ const cleanEq2 = eq2.trim();
2172
+ // For linear 2x2 systems, we can use algebraic manipulation
2173
+ // Example: x + y = 5 and x - y = 1
2174
+ // Add equations: 2x = 6, so x = 3
2175
+ // Subtract: 2y = 4, so y = 2
2176
+ if (cleanEq1.includes('+') && cleanEq2.includes('-') &&
2177
+ cleanEq1.includes('=') && cleanEq2.includes('=')) {
2178
+ // For x + y = 5 and x - y = 1:
2179
+ // Adding gives: (x + y) + (x - y) = 5 + 1 => 2x = 6
2180
+ // Subtracting gives: (x + y) - (x - y) = 5 - 1 => 2y = 4
2181
+ // Extract right-hand sides
2182
+ const rhs1 = cleanEq1.split('=')[1]?.trim();
2183
+ const rhs2 = cleanEq2.split('=')[1]?.trim();
2184
+ if (rhs1 && rhs2) {
2185
+ // Since Solve() doesn't work, use direct arithmetic calculation
2186
+ const sum = parseFloat(rhs1) + parseFloat(rhs2); // 5 + 1 = 6
2187
+ const diff = parseFloat(rhs1) - parseFloat(rhs2); // 5 - 1 = 4
2188
+ if (!isNaN(sum) && !isNaN(diff)) {
2189
+ // Calculate solutions: x = sum/2, y = diff/2
2190
+ const xSolution = sum / 2; // 6/2 = 3
2191
+ const ySolution = diff / 2; // 4/2 = 2
2192
+ // Create calculation objects to show the work
2193
+ const sumObjectName = `sum_calc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2194
+ const diffObjectName = `diff_calc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2195
+ const xSolObjectName = `x_solution_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2196
+ const ySolObjectName = `y_solution_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2197
+ // Create arithmetic calculation objects in GeoGebra to verify
2198
+ const sumCommand = `${sumObjectName} = ${rhs1} + ${rhs2}`;
2199
+ const diffCommand = `${diffObjectName} = ${rhs1} - ${rhs2}`;
2200
+ const xSolCommand = `${xSolObjectName} = ${sumObjectName} / 2`;
2201
+ const ySolCommand = `${ySolObjectName} = ${diffObjectName} / 2`;
2202
+ logger_1.default.info(`Arithmetic solution: ${sumCommand}, ${diffCommand}, ${xSolCommand}, ${ySolCommand}`);
2203
+ // Execute the arithmetic calculations
2204
+ const sumResult = await instance.evalCommand(sumCommand);
2205
+ const diffResult = await instance.evalCommand(diffCommand);
2206
+ const xSolResult = await instance.evalCommand(xSolCommand);
2207
+ const ySolResult = await instance.evalCommand(ySolCommand);
2208
+ if (sumResult.success && diffResult.success && xSolResult.success && ySolResult.success) {
2209
+ // Get the calculated values to verify our arithmetic
2210
+ const sumValue = await instance.getValueString(sumObjectName);
2211
+ const diffValue = await instance.getValueString(diffObjectName);
2212
+ const xSolValue = await instance.getValueString(xSolObjectName);
2213
+ const ySolValue = await instance.getValueString(ySolObjectName);
2214
+ // Clean up temporary calculation objects
2215
+ await instance.deleteObject(sumObjectName);
2216
+ await instance.deleteObject(diffObjectName);
2217
+ await instance.deleteObject(xSolObjectName);
2218
+ await instance.deleteObject(ySolObjectName);
2219
+ // Build solution response
2220
+ const parsedSolution = {};
2221
+ parsedSolution[var1] = xSolution.toString();
2222
+ parsedSolution[var2] = ySolution.toString();
2223
+ const verificationSteps = {
2224
+ step1: `Add equations: (${cleanEq1}) + (${cleanEq2}) → 2${var1} = ${sum}`,
2225
+ step2: `Subtract equations: (${cleanEq1}) - (${cleanEq2}) → 2${var2} = ${diff}`,
2226
+ step3: `Therefore: ${var1} = ${sum}/2 = ${xSolution}`,
2227
+ step4: `Therefore: ${var2} = ${diff}/2 = ${ySolution}`
2228
+ };
2229
+ const geogebraVerification = {
2230
+ sumCalculation: sumValue,
2231
+ diffCalculation: diffValue,
2232
+ xCalculation: xSolValue,
2233
+ yCalculation: ySolValue
2234
+ };
2235
+ return {
2236
+ content: [{
2237
+ type: 'text',
2238
+ text: JSON.stringify({
2239
+ success: true,
2240
+ equations,
2241
+ variables,
2242
+ method: 'arithmetic_calculation',
2243
+ solution: parsedSolution,
2244
+ verificationSteps,
2245
+ geogebraVerification,
2246
+ commands: [sumCommand, diffCommand, xSolCommand, ySolCommand],
2247
+ note: 'Solution calculated using direct arithmetic since GeoGebra Solve() commands are not supported in this version'
2248
+ }, null, 2)
2249
+ }]
2250
+ };
2251
+ }
2252
+ }
2253
+ }
2254
+ }
2255
+ // Fallback: Try to solve using numerical approximation
2256
+ logger_1.default.info('Attempting fallback solution method');
2257
+ // Use simple substitution for well-known patterns
2258
+ if (cleanEq1.includes('x + y') && cleanEq2.includes('x - y')) {
2259
+ // Known pattern: x + y = a, x - y = b
2260
+ // Solution: x = (a + b)/2, y = (a - b)/2
2261
+ const eq1Parts = cleanEq1.split('=');
2262
+ const eq2Parts = cleanEq2.split('=');
2263
+ if (eq1Parts.length === 2 && eq2Parts.length === 2 && eq1Parts[1] && eq2Parts[1]) {
2264
+ const a = parseFloat(eq1Parts[1].trim());
2265
+ const b = parseFloat(eq2Parts[1].trim());
2266
+ if (!isNaN(a) && !isNaN(b)) {
2267
+ const x = (a + b) / 2;
2268
+ const y = (a - b) / 2;
2269
+ const solutionMap = {};
2270
+ solutionMap[var1] = x.toString();
2271
+ solutionMap[var2] = y.toString();
2272
+ return {
2273
+ content: [{
2274
+ type: 'text',
2275
+ text: JSON.stringify({
2276
+ success: true,
2277
+ equations,
2278
+ variables,
2279
+ method: 'analytical_solution',
2280
+ solution: solutionMap,
2281
+ verification: `${var1} = ${x}, ${var2} = ${y}`
2282
+ }, null, 2)
2283
+ }]
2284
+ };
2285
+ }
2286
+ }
2287
+ }
2288
+ // If all methods fail, provide a clear error message
2289
+ throw new Error('Unable to solve this system of equations. The GeoGebra instance may not support the required CAS operations for multi-variable systems. Consider solving manually or using simpler equation formats.');
2290
+ }
2291
+ catch (algebraError) {
2292
+ logger_1.default.error('Algebraic substitution failed', algebraError);
2293
+ throw new Error(`System solving failed: ${algebraError instanceof Error ? algebraError.message : String(algebraError)}`);
2294
+ }
2295
+ }
2296
+ else {
2297
+ // For non-2x2 systems, provide appropriate error
2298
+ throw new Error(`Currently only 2x2 linear systems are supported. Received ${equations.length} equations with ${variables.length} variables.`);
2299
+ }
2300
+ }
2301
+ catch (error) {
2302
+ logger_1.default.error('Failed to solve system of equations', error);
2303
+ return {
2304
+ content: [{
2305
+ type: 'text',
2306
+ text: JSON.stringify({
2307
+ success: false,
2308
+ error: error instanceof Error ? error.message : String(error),
2309
+ note: 'GEB-20 Fix: This version uses algebraic substitution instead of direct GeoGebra Solve() syntax which is not supported in this GeoGebra version.'
2310
+ }, null, 2)
2311
+ }],
2312
+ isError: true
2313
+ };
2314
+ }
2315
+ }
2316
+ },
2317
+ {
2318
+ tool: {
2319
+ name: 'geogebra_differentiate',
2320
+ description: 'Compute the derivative of an expression using GeoGebra\'s CAS',
2321
+ inputSchema: {
2322
+ type: 'object',
2323
+ properties: {
2324
+ expression: {
2325
+ type: 'string',
2326
+ description: 'The expression to differentiate (e.g., "x^2", "sin(x)", "x^3 + 2x + 1")'
2327
+ },
2328
+ variable: {
2329
+ type: 'string',
2330
+ description: 'Variable to differentiate with respect to (optional, defaults to "x")'
2331
+ }
2332
+ },
2333
+ required: ['expression']
2334
+ }
2335
+ },
2336
+ handler: async (params) => {
2337
+ try {
2338
+ const expression = params['expression'];
2339
+ const variable = params['variable'] || 'x';
2340
+ // Validate expression
2341
+ const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
2342
+ if (!expressionValidation.isValid) {
2343
+ throw new Error(`Invalid expression: ${expressionValidation.error}`);
2344
+ }
2345
+ // Validate variable
2346
+ const variableValidation = (0, validation_1.validateVariableName)(variable);
2347
+ if (!variableValidation.isValid) {
2348
+ throw new Error(`Invalid variable: ${variableValidation.error}`);
2349
+ }
2350
+ const instance = await exports.instancePool.getDefaultInstance();
2351
+ // Create a unique object name for the derivative result
2352
+ const derivativeObjectName = `derivative_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2353
+ // Create the derivative command with named object
2354
+ const command = `${derivativeObjectName} = Derivative(${expression}, ${variable})`;
2355
+ // Execute the command to create the derivative object
2356
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
2357
+ if (!result.success) {
2358
+ throw new Error(result.error || 'Failed to compute derivative');
2359
+ }
2360
+ // Check if the derivative object was created
2361
+ const derivativeExists = await instance.exists(derivativeObjectName);
2362
+ if (!derivativeExists) {
2363
+ throw new Error('Derivative object was not created - the expression may be invalid');
2364
+ }
2365
+ // Get the derivative value as a string
2366
+ const derivativeValue = await instance.getValueString(derivativeObjectName);
2367
+ // Clean up the temporary object
2368
+ await instance.deleteObject(derivativeObjectName);
2369
+ return {
2370
+ content: [{
2371
+ type: 'text',
2372
+ text: JSON.stringify({
2373
+ success: true,
2374
+ expression,
2375
+ variable,
2376
+ command,
2377
+ derivative: derivativeValue
2378
+ }, null, 2)
2379
+ }]
2380
+ };
2381
+ }
2382
+ catch (error) {
2383
+ logger_1.default.error('Failed to compute derivative', error);
2384
+ return {
2385
+ content: [{
2386
+ type: 'text',
2387
+ text: JSON.stringify({
2388
+ success: false,
2389
+ error: error instanceof Error ? error.message : String(error)
2390
+ }, null, 2)
2391
+ }],
2392
+ isError: true
2393
+ };
2394
+ }
2395
+ }
2396
+ },
2397
+ {
2398
+ tool: {
2399
+ name: 'geogebra_integrate',
2400
+ description: 'Compute the integral of an expression using GeoGebra\'s CAS',
2401
+ inputSchema: {
2402
+ type: 'object',
2403
+ properties: {
2404
+ expression: {
2405
+ type: 'string',
2406
+ description: 'The expression to integrate (e.g., "x^2", "sin(x)", "2x + 1")'
2407
+ },
2408
+ variable: {
2409
+ type: 'string',
2410
+ description: 'Variable to integrate with respect to (optional, defaults to "x")'
2411
+ }
2412
+ },
2413
+ required: ['expression']
2414
+ }
2415
+ },
2416
+ handler: async (params) => {
2417
+ try {
2418
+ const expression = params['expression'];
2419
+ const variable = params['variable'] || 'x';
2420
+ // Validate expression
2421
+ const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
2422
+ if (!expressionValidation.isValid) {
2423
+ throw new Error(`Invalid expression: ${expressionValidation.error}`);
2424
+ }
2425
+ // Validate variable
2426
+ const variableValidation = (0, validation_1.validateVariableName)(variable);
2427
+ if (!variableValidation.isValid) {
2428
+ throw new Error(`Invalid variable: ${variableValidation.error}`);
2429
+ }
2430
+ const instance = await exports.instancePool.getDefaultInstance();
2431
+ // Create a unique object name for the integral result
2432
+ const integralObjectName = `integral_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2433
+ // Create the integral command with named object
2434
+ const command = `${integralObjectName} = Integral(${expression}, ${variable})`;
2435
+ // Execute the command to create the integral object
2436
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
2437
+ if (!result.success) {
2438
+ throw new Error(result.error || 'Failed to compute integral');
2439
+ }
2440
+ // Check if the integral object was created
2441
+ const integralExists = await instance.exists(integralObjectName);
2442
+ if (!integralExists) {
2443
+ throw new Error('Integral object was not created - the expression may be invalid');
2444
+ }
2445
+ // Get the integral value as a string
2446
+ const integralValue = await instance.getValueString(integralObjectName);
2447
+ // Clean up the temporary object
2448
+ await instance.deleteObject(integralObjectName);
2449
+ return {
2450
+ content: [{
2451
+ type: 'text',
2452
+ text: JSON.stringify({
2453
+ success: true,
2454
+ expression,
2455
+ variable,
2456
+ command,
2457
+ integral: integralValue
2458
+ }, null, 2)
2459
+ }]
2460
+ };
2461
+ }
2462
+ catch (error) {
2463
+ logger_1.default.error('Failed to compute integral', error);
2464
+ return {
2465
+ content: [{
2466
+ type: 'text',
2467
+ text: JSON.stringify({
2468
+ success: false,
2469
+ error: error instanceof Error ? error.message : String(error)
2470
+ }, null, 2)
2471
+ }],
2472
+ isError: true
2473
+ };
2474
+ }
2475
+ }
2476
+ },
2477
+ {
2478
+ tool: {
2479
+ name: 'geogebra_simplify',
2480
+ description: 'Simplify an algebraic expression using GeoGebra\'s CAS',
2481
+ inputSchema: {
2482
+ type: 'object',
2483
+ properties: {
2484
+ expression: {
2485
+ type: 'string',
2486
+ description: 'The expression to simplify (e.g., "x^2 + 2x + 1", "(x+1)^2", "sin^2(x) + cos^2(x)")'
2487
+ }
2488
+ },
2489
+ required: ['expression']
2490
+ }
2491
+ },
2492
+ handler: async (params) => {
2493
+ try {
2494
+ const expression = params['expression'];
2495
+ // Validate expression
2496
+ const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
2497
+ if (!expressionValidation.isValid) {
2498
+ throw new Error(`Invalid expression: ${expressionValidation.error}`);
2499
+ }
2500
+ const instance = await exports.instancePool.getDefaultInstance();
2501
+ // Create a unique object name for the simplified result
2502
+ const simplifiedObjectName = `simplified_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2503
+ // Create the simplify command with named object
2504
+ const command = `${simplifiedObjectName} = Simplify(${expression})`;
2505
+ // Execute the command to create the simplified object
2506
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
2507
+ if (!result.success) {
2508
+ throw new Error(result.error || 'Failed to simplify expression');
2509
+ }
2510
+ // Check if the simplified object was created
2511
+ const simplifiedExists = await instance.exists(simplifiedObjectName);
2512
+ if (!simplifiedExists) {
2513
+ throw new Error('Simplified object was not created - the expression may be invalid');
2514
+ }
2515
+ // Get the simplified value as a string
2516
+ const simplifiedValue = await instance.getValueString(simplifiedObjectName);
2517
+ // Clean up the temporary object
2518
+ await instance.deleteObject(simplifiedObjectName);
2519
+ return {
2520
+ content: [{
2521
+ type: 'text',
2522
+ text: JSON.stringify({
2523
+ success: true,
2524
+ expression,
2525
+ command,
2526
+ simplified: simplifiedValue
2527
+ }, null, 2)
2528
+ }]
2529
+ };
2530
+ }
2531
+ catch (error) {
2532
+ logger_1.default.error('Failed to simplify expression', error);
2533
+ return {
2534
+ content: [{
2535
+ type: 'text',
2536
+ text: JSON.stringify({
2537
+ success: false,
2538
+ error: error instanceof Error ? error.message : String(error)
2539
+ }, null, 2)
2540
+ }],
2541
+ isError: true
2542
+ };
2543
+ }
2544
+ }
2545
+ },
2546
+ // Animation Tools - GEB-7 Implementation
2547
+ {
2548
+ tool: {
2549
+ name: 'geogebra_create_slider',
2550
+ description: 'Create an interactive slider for parameter control and animation',
2551
+ inputSchema: {
2552
+ type: 'object',
2553
+ properties: {
2554
+ name: {
2555
+ type: 'string',
2556
+ description: 'Name of the slider (e.g., "a", "t", "speed")'
2557
+ },
2558
+ min: {
2559
+ type: 'number',
2560
+ description: 'Minimum value of the slider'
2561
+ },
2562
+ max: {
2563
+ type: 'number',
2564
+ description: 'Maximum value of the slider'
2565
+ },
2566
+ increment: {
2567
+ type: 'number',
2568
+ description: 'Step size for the slider (optional, defaults to 0.1)'
2569
+ },
2570
+ defaultValue: {
2571
+ type: 'number',
2572
+ description: 'Initial value of the slider (optional, defaults to minimum value)'
2573
+ },
2574
+ width: {
2575
+ type: 'number',
2576
+ description: 'Width of the slider in pixels (optional, defaults to 150)'
2577
+ },
2578
+ isAngle: {
2579
+ type: 'boolean',
2580
+ description: 'Whether the slider represents an angle (optional, defaults to false)'
2581
+ },
2582
+ horizontal: {
2583
+ type: 'boolean',
2584
+ description: 'Whether the slider is horizontal (optional, defaults to true)'
2585
+ }
2586
+ },
2587
+ required: ['name', 'min', 'max']
2588
+ }
2589
+ },
2590
+ handler: async (params) => {
2591
+ try {
2592
+ const name = params['name'];
2593
+ const min = params['min'];
2594
+ const max = params['max'];
2595
+ const increment = params['increment'] || 0.1;
2596
+ const defaultValue = params['defaultValue'] || min;
2597
+ const width = params['width'] || 150;
2598
+ const isAngle = params['isAngle'] || false;
2599
+ const horizontal = params['horizontal'] !== false;
2600
+ // Validate parameters
2601
+ const validation = (0, validation_1.validateSliderParameters)(name, min, max, increment, defaultValue);
2602
+ if (!validation.isValid) {
2603
+ throw new Error(validation.error);
2604
+ }
2605
+ const instance = await exports.instancePool.getDefaultInstance();
2606
+ // Create GeoGebra slider command
2607
+ const command = `${name} = Slider(${min}, ${max}, ${increment}, 1, ${width}, ${isAngle}, ${horizontal}, false, false)`;
2608
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
2609
+ if (!result.success) {
2610
+ throw new Error(result.error || 'Failed to create slider');
2611
+ }
2612
+ // Set the default value
2613
+ await instance.setValue(name, defaultValue);
2614
+ const sliderInfo = await instance.getObjectInfo(name);
2615
+ return {
2616
+ content: [{
2617
+ type: 'text',
2618
+ text: JSON.stringify({
2619
+ success: true,
2620
+ command,
2621
+ slider: {
2622
+ name,
2623
+ min,
2624
+ max,
2625
+ increment,
2626
+ currentValue: defaultValue,
2627
+ width,
2628
+ isAngle,
2629
+ horizontal,
2630
+ info: sliderInfo
2631
+ }
2632
+ }, null, 2)
2633
+ }]
2634
+ };
2635
+ }
2636
+ catch (error) {
2637
+ logger_1.default.error('Failed to create slider', error);
2638
+ return {
2639
+ content: [{
2640
+ type: 'text',
2641
+ text: JSON.stringify({
2642
+ success: false,
2643
+ error: error instanceof Error ? error.message : String(error)
2644
+ }, null, 2)
2645
+ }],
2646
+ isError: true
2647
+ };
2648
+ }
2649
+ }
2650
+ },
2651
+ {
2652
+ tool: {
2653
+ name: 'geogebra_animate_parameter',
2654
+ description: 'Configure an object (especially sliders) for parameter-based animation',
2655
+ inputSchema: {
2656
+ type: 'object',
2657
+ properties: {
2658
+ objectName: {
2659
+ type: 'string',
2660
+ description: 'Name of the object to animate (typically a slider or point on a path)'
2661
+ },
2662
+ animate: {
2663
+ type: 'boolean',
2664
+ description: 'Whether to enable or disable animation for this object'
2665
+ },
2666
+ speed: {
2667
+ type: 'number',
2668
+ description: 'Animation speed (optional, defaults to 1, valid range: 0.1 to 10)'
2669
+ },
2670
+ direction: {
2671
+ type: 'string',
2672
+ enum: ['forward', 'backward', 'oscillating'],
2673
+ description: 'Animation direction (optional, defaults to "forward")'
2674
+ }
2675
+ },
2676
+ required: ['objectName', 'animate']
2677
+ }
2678
+ },
2679
+ handler: async (params) => {
2680
+ try {
2681
+ const objectName = params['objectName'];
2682
+ const animate = params['animate'];
2683
+ const speed = params['speed'] || 1;
2684
+ const direction = params['direction'] || 'forward';
2685
+ // Validate parameters
2686
+ const nameValidation = (0, validation_1.validateObjectName)(objectName);
2687
+ if (!nameValidation.isValid) {
2688
+ throw new Error(`Invalid object name: ${nameValidation.error}`);
2689
+ }
2690
+ const speedValidation = (0, validation_1.validateAnimationSpeed)(speed);
2691
+ if (!speedValidation.isValid) {
2692
+ throw new Error(speedValidation.error);
2693
+ }
2694
+ const directionValidation = (0, validation_1.validateAnimationDirection)(direction);
2695
+ if (!directionValidation.isValid) {
2696
+ throw new Error(directionValidation.error);
2697
+ }
2698
+ const instance = await exports.instancePool.getDefaultInstance();
2699
+ // Check if object exists
2700
+ const exists = await instance.exists(objectName);
2701
+ if (!exists) {
2702
+ throw new Error(`Object ${objectName} does not exist`);
2703
+ }
2704
+ // Set animation properties
2705
+ await instance.setAnimating(objectName, animate);
2706
+ if (animate) {
2707
+ await instance.setAnimationSpeed(objectName, speed);
2708
+ }
2709
+ const objectInfo = await instance.getObjectInfo(objectName);
2710
+ return {
2711
+ content: [{
2712
+ type: 'text',
2713
+ text: JSON.stringify({
2714
+ success: true,
2715
+ animation: {
2716
+ objectName,
2717
+ animate,
2718
+ speed: animate ? speed : null,
2719
+ direction,
2720
+ objectInfo
2721
+ }
2722
+ }, null, 2)
2723
+ }]
2724
+ };
2725
+ }
2726
+ catch (error) {
2727
+ logger_1.default.error('Failed to configure animation', error);
2728
+ return {
2729
+ content: [{
2730
+ type: 'text',
2731
+ text: JSON.stringify({
2732
+ success: false,
2733
+ error: error instanceof Error ? error.message : String(error)
2734
+ }, null, 2)
2735
+ }],
2736
+ isError: true
2737
+ };
2738
+ }
2739
+ }
2740
+ },
2741
+ {
2742
+ tool: {
2743
+ name: 'geogebra_trace_object',
2744
+ description: 'Enable or disable trace for an object to visualize its path during animation',
2745
+ inputSchema: {
2746
+ type: 'object',
2747
+ properties: {
2748
+ objectName: {
2749
+ type: 'string',
2750
+ description: 'Name of the object to trace (typically animated points or curves)'
2751
+ },
2752
+ enableTrace: {
2753
+ type: 'boolean',
2754
+ description: 'Whether to enable or disable tracing for this object'
2755
+ }
2756
+ },
2757
+ required: ['objectName', 'enableTrace']
2758
+ }
2759
+ },
2760
+ handler: async (params) => {
2761
+ try {
2762
+ const objectName = params['objectName'];
2763
+ const enableTrace = params['enableTrace'];
2764
+ // Validate object name
2765
+ const nameValidation = (0, validation_1.validateObjectName)(objectName);
2766
+ if (!nameValidation.isValid) {
2767
+ throw new Error(`Invalid object name: ${nameValidation.error}`);
2768
+ }
2769
+ const instance = await exports.instancePool.getDefaultInstance();
2770
+ // Check if object exists
2771
+ const exists = await instance.exists(objectName);
2772
+ if (!exists) {
2773
+ throw new Error(`Object ${objectName} does not exist`);
2774
+ }
2775
+ // Set trace
2776
+ await instance.setTrace(objectName, enableTrace);
2777
+ const objectInfo = await instance.getObjectInfo(objectName);
2778
+ return {
2779
+ content: [{
2780
+ type: 'text',
2781
+ text: JSON.stringify({
2782
+ success: true,
2783
+ trace: {
2784
+ objectName,
2785
+ enableTrace,
2786
+ objectInfo
2787
+ }
2788
+ }, null, 2)
2789
+ }]
2790
+ };
2791
+ }
2792
+ catch (error) {
2793
+ logger_1.default.error('Failed to set trace', error);
2794
+ return {
2795
+ content: [{
2796
+ type: 'text',
2797
+ text: JSON.stringify({
2798
+ success: false,
2799
+ error: error instanceof Error ? error.message : String(error)
2800
+ }, null, 2)
2801
+ }],
2802
+ isError: true
2803
+ };
2804
+ }
2805
+ }
2806
+ },
2807
+ {
2808
+ tool: {
2809
+ name: 'geogebra_start_animation',
2810
+ description: 'Start animation for all objects with animation enabled',
2811
+ inputSchema: {
2812
+ type: 'object',
2813
+ properties: {},
2814
+ required: []
2815
+ }
2816
+ },
2817
+ handler: async (_params) => {
2818
+ try {
2819
+ const instance = await exports.instancePool.getDefaultInstance();
2820
+ await instance.startAnimation();
2821
+ const isRunning = await instance.isAnimationRunning();
2822
+ return {
2823
+ content: [{
2824
+ type: 'text',
2825
+ text: JSON.stringify({
2826
+ success: true,
2827
+ action: 'start_animation',
2828
+ isRunning
2829
+ }, null, 2)
2830
+ }]
2831
+ };
2832
+ }
2833
+ catch (error) {
2834
+ logger_1.default.error('Failed to start animation', error);
2835
+ return {
2836
+ content: [{
2837
+ type: 'text',
2838
+ text: JSON.stringify({
2839
+ success: false,
2840
+ error: error instanceof Error ? error.message : String(error)
2841
+ }, null, 2)
2842
+ }],
2843
+ isError: true
2844
+ };
2845
+ }
2846
+ }
2847
+ },
2848
+ {
2849
+ tool: {
2850
+ name: 'geogebra_stop_animation',
2851
+ description: 'Stop all currently running animations',
2852
+ inputSchema: {
2853
+ type: 'object',
2854
+ properties: {},
2855
+ required: []
2856
+ }
2857
+ },
2858
+ handler: async (_params) => {
2859
+ try {
2860
+ const instance = await exports.instancePool.getDefaultInstance();
2861
+ await instance.stopAnimation();
2862
+ const isRunning = await instance.isAnimationRunning();
2863
+ return {
2864
+ content: [{
2865
+ type: 'text',
2866
+ text: JSON.stringify({
2867
+ success: true,
2868
+ action: 'stop_animation',
2869
+ isRunning
2870
+ }, null, 2)
2871
+ }]
2872
+ };
2873
+ }
2874
+ catch (error) {
2875
+ logger_1.default.error('Failed to stop animation', error);
2876
+ return {
2877
+ content: [{
2878
+ type: 'text',
2879
+ text: JSON.stringify({
2880
+ success: false,
2881
+ error: error instanceof Error ? error.message : String(error)
2882
+ }, null, 2)
2883
+ }],
2884
+ isError: true
2885
+ };
2886
+ }
2887
+ }
2888
+ },
2889
+ {
2890
+ tool: {
2891
+ name: 'geogebra_animation_status',
2892
+ description: 'Check the current status of animations',
2893
+ inputSchema: {
2894
+ type: 'object',
2895
+ properties: {},
2896
+ required: []
2897
+ }
2898
+ },
2899
+ handler: async (_params) => {
2900
+ try {
2901
+ const instance = await exports.instancePool.getDefaultInstance();
2902
+ const isRunning = await instance.isAnimationRunning();
2903
+ return {
2904
+ content: [{
2905
+ type: 'text',
2906
+ text: JSON.stringify({
2907
+ success: true,
2908
+ action: 'check_animation_status',
2909
+ isRunning
2910
+ }, null, 2)
2911
+ }]
2912
+ };
2913
+ }
2914
+ catch (error) {
2915
+ logger_1.default.error('Failed to check animation status', error);
2916
+ return {
2917
+ content: [{
2918
+ type: 'text',
2919
+ text: JSON.stringify({
2920
+ success: false,
2921
+ error: error instanceof Error ? error.message : String(error)
2922
+ }, null, 2)
2923
+ }],
2924
+ isError: true
2925
+ };
2926
+ }
2927
+ }
2928
+ },
2929
+ {
2930
+ tool: {
2931
+ name: 'geogebra_export_animation',
2932
+ description: 'Export an animation as a sequence of frames for GIF creation',
2933
+ inputSchema: {
2934
+ type: 'object',
2935
+ properties: {
2936
+ frameCount: {
2937
+ type: 'number',
2938
+ description: 'Number of frames to capture (default: 30, max: 300)'
2939
+ },
2940
+ frameDelay: {
2941
+ type: 'number',
2942
+ description: 'Delay between frames in milliseconds (default: 100)'
2943
+ },
2944
+ totalDuration: {
2945
+ type: 'number',
2946
+ description: 'Total animation duration in seconds (optional, calculated from frameCount and frameDelay if not provided)'
2947
+ },
2948
+ width: {
2949
+ type: 'number',
2950
+ description: 'Width of exported frames (optional, defaults to current view width)'
2951
+ },
2952
+ height: {
2953
+ type: 'number',
2954
+ description: 'Height of exported frames (optional, defaults to current view height)'
2955
+ },
2956
+ scale: {
2957
+ type: 'number',
2958
+ description: 'Scale factor for export quality (default: 1, range: 0.5 to 3)'
2959
+ },
2960
+ format: {
2961
+ type: 'string',
2962
+ enum: ['png', 'svg'],
2963
+ description: 'Export format for frames (default: png)'
2964
+ }
2965
+ },
2966
+ required: []
2967
+ }
2968
+ },
2969
+ handler: async (params) => {
2970
+ try {
2971
+ const frameCount = params['frameCount'] || 30;
2972
+ const frameDelay = params['frameDelay'] || 100;
2973
+ const totalDuration = params['totalDuration'] || ((frameCount * frameDelay) / 1000);
2974
+ const width = params['width'];
2975
+ const height = params['height'];
2976
+ const scale = Math.min(Math.max(params['scale'] || 1, 0.5), 3);
2977
+ const format = params['format'] || 'png';
2978
+ // Validate export parameters FIRST before any other operations
2979
+ const validation = (0, validation_1.validateAnimationExportParameters)(frameCount, frameDelay, totalDuration);
2980
+ if (!validation.isValid) {
2981
+ throw new Error(validation.error);
2982
+ }
2983
+ const instance = await exports.instancePool.getDefaultInstance();
2984
+ // GEB-17: Use the new enhanced exportAnimation method with better error handling
2985
+ const frameRate = 1000 / frameDelay; // Convert delay to frame rate
2986
+ const duration = totalDuration * 1000; // Convert to milliseconds
2987
+ const animationOptions = {
2988
+ duration,
2989
+ frameRate,
2990
+ format: 'frames', // Always export as frames for now
2991
+ width,
2992
+ height
2993
+ };
2994
+ logger_1.default.info(`Starting animation export: ${frameCount} frames over ${totalDuration}s using enhanced method`);
2995
+ // Use the enhanced exportAnimation method which includes all GEB-17 improvements
2996
+ const frames = await instance.exportAnimation(animationOptions);
2997
+ // Ensure we have the expected array of frames
2998
+ if (!Array.isArray(frames)) {
2999
+ throw new Error('Animation export did not return frame array as expected');
3000
+ }
3001
+ logger_1.default.info(`Animation export completed: ${frames.length} frames captured`);
3002
+ return {
3003
+ content: [{
3004
+ type: 'text',
3005
+ text: JSON.stringify({
3006
+ success: true,
3007
+ export: {
3008
+ frameCount: frames.length,
3009
+ frameDelay,
3010
+ totalDuration,
3011
+ format,
3012
+ scale,
3013
+ dimensions: width && height ? { width, height } : null,
3014
+ frames: frames.slice(0, 3), // Only return first 3 frames in response for brevity
3015
+ note: `Captured ${frames.length} frames using enhanced GEB-17 export method. In a full implementation, these would be processed into a GIF file.`,
3016
+ implementation_note: "Frame data contains base64 encoded images that can be processed into GIF format using libraries like 'gif-encoder' or 'gifski'",
3017
+ geb17_improvements: "Enhanced with better error handling, applet validation, and retry logic"
3018
+ }
3019
+ }, null, 2)
3020
+ }]
3021
+ };
3022
+ }
3023
+ catch (error) {
3024
+ logger_1.default.error('Failed to export animation', error);
3025
+ return {
3026
+ content: [{
3027
+ type: 'text',
3028
+ text: JSON.stringify({
3029
+ success: false,
3030
+ error: error instanceof Error ? error.message : String(error),
3031
+ geb17_note: "Enhanced error handling provides more detailed failure information"
3032
+ }, null, 2)
3033
+ }],
3034
+ isError: true
3035
+ };
3036
+ }
3037
+ }
3038
+ },
3039
+ {
3040
+ tool: {
3041
+ name: 'geogebra_animation_demo',
3042
+ description: 'Create a comprehensive animation demonstration with parametric curves, traced objects, and animated sliders',
3043
+ inputSchema: {
3044
+ type: 'object',
3045
+ properties: {
3046
+ demoType: {
3047
+ type: 'string',
3048
+ enum: ['parametric_spiral', 'pendulum', 'wave_function', 'circle_trace'],
3049
+ description: 'Type of animation demo to create (default: parametric_spiral)'
3050
+ },
3051
+ animationSpeed: {
3052
+ type: 'number',
3053
+ description: 'Animation speed (default: 1, range: 0.1 to 5)'
3054
+ }
3055
+ },
3056
+ required: []
3057
+ }
3058
+ },
3059
+ handler: async (params) => {
3060
+ try {
3061
+ const demoType = params['demoType'] || 'parametric_spiral';
3062
+ const animationSpeed = Math.min(Math.max(params['animationSpeed'] || 1, 0.1), 5);
3063
+ const instance = await exports.instancePool.getDefaultInstance();
3064
+ // Clear any existing construction
3065
+ await instance.newConstruction();
3066
+ let createdObjects = [];
3067
+ let animatedObjects = [];
3068
+ switch (demoType) {
3069
+ case 'parametric_spiral':
3070
+ // Create time parameter slider
3071
+ await instance.evalCommand('t = Slider(0, 4*pi, 0.1, 1, 200, false, true, false, false)');
3072
+ await instance.setValue('t', 0);
3073
+ createdObjects.push('t');
3074
+ // Create parametric spiral
3075
+ await instance.evalCommand('spiralX(s) = s * cos(s)');
3076
+ await instance.evalCommand('spiralY(s) = s * sin(s)');
3077
+ await instance.evalCommand('spiral = Curve(spiralX(s), spiralY(s), s, 0, t)');
3078
+ createdObjects.push('spiral');
3079
+ // Create moving point on spiral
3080
+ await instance.evalCommand('P = (spiralX(t), spiralY(t))');
3081
+ createdObjects.push('P');
3082
+ // Enable animation and tracing
3083
+ await instance.setAnimating('t', true);
3084
+ await instance.setAnimationSpeed('t', animationSpeed);
3085
+ await instance.setTrace('P', true);
3086
+ animatedObjects.push('t');
3087
+ break;
3088
+ case 'pendulum':
3089
+ // Create angle slider
3090
+ await instance.evalCommand('angle = Slider(-pi/2, pi/2, 0.05, 1, 200, true, true, false, false)');
3091
+ await instance.setValue('angle', 0);
3092
+ createdObjects.push('angle');
3093
+ // Create pendulum setup
3094
+ await instance.evalCommand('origin = (0, 0)');
3095
+ await instance.evalCommand('length = 3');
3096
+ await instance.evalCommand('pendulumX = length * sin(angle)');
3097
+ await instance.evalCommand('pendulumY = -length * cos(angle)');
3098
+ await instance.evalCommand('bob = (pendulumX, pendulumY)');
3099
+ await instance.evalCommand('rod = Segment(origin, bob)');
3100
+ createdObjects.push('origin', 'bob', 'rod');
3101
+ // Enable animation and tracing
3102
+ await instance.setAnimating('angle', true);
3103
+ await instance.setAnimationSpeed('angle', animationSpeed);
3104
+ await instance.setTrace('bob', true);
3105
+ animatedObjects.push('angle');
3106
+ break;
3107
+ case 'wave_function':
3108
+ // Create time and frequency sliders
3109
+ await instance.evalCommand('time = Slider(0, 4*pi, 0.1, 1, 150, false, true, false, false)');
3110
+ await instance.evalCommand('freq = Slider(0.5, 3, 0.1, 1, 150, false, true, false, false)');
3111
+ await instance.setValue('time', 0);
3112
+ await instance.setValue('freq', 1);
3113
+ createdObjects.push('time', 'freq');
3114
+ // Create wave function
3115
+ await instance.evalCommand('wave(x) = sin(freq * x + time)');
3116
+ await instance.evalCommand('f = wave(x)');
3117
+ createdObjects.push('f');
3118
+ // Create a point that moves along the wave
3119
+ await instance.evalCommand('wavePoint = (time, sin(freq * time + time))');
3120
+ createdObjects.push('wavePoint');
3121
+ // Enable animation and tracing
3122
+ await instance.setAnimating('time', true);
3123
+ await instance.setAnimationSpeed('time', animationSpeed);
3124
+ await instance.setTrace('wavePoint', true);
3125
+ animatedObjects.push('time');
3126
+ break;
3127
+ case 'circle_trace':
3128
+ // Create angle slider
3129
+ await instance.evalCommand('theta = Slider(0, 4*pi, 0.1, 1, 200, true, true, false, false)');
3130
+ await instance.setValue('theta', 0);
3131
+ createdObjects.push('theta');
3132
+ // Create circles and tracing points
3133
+ await instance.evalCommand('radius1 = 2');
3134
+ await instance.evalCommand('radius2 = 1');
3135
+ await instance.evalCommand('center1 = (0, 0)');
3136
+ await instance.evalCommand('center2 = (radius1 * cos(theta), radius1 * sin(theta))');
3137
+ await instance.evalCommand('circle1 = Circle(center1, radius1)');
3138
+ await instance.evalCommand('circle2 = Circle(center2, radius2)');
3139
+ createdObjects.push('circle1', 'circle2', 'center1', 'center2');
3140
+ // Create epicycloid trace point
3141
+ await instance.evalCommand('traceX = (radius1 + radius2) * cos(theta) - radius2 * cos((radius1 + radius2) / radius2 * theta)');
3142
+ await instance.evalCommand('traceY = (radius1 + radius2) * sin(theta) - radius2 * sin((radius1 + radius2) / radius2 * theta)');
3143
+ await instance.evalCommand('tracePoint = (traceX, traceY)');
3144
+ createdObjects.push('tracePoint');
3145
+ // Enable animation and tracing
3146
+ await instance.setAnimating('theta', true);
3147
+ await instance.setAnimationSpeed('theta', animationSpeed);
3148
+ await instance.setTrace('tracePoint', true);
3149
+ await instance.setTrace('center2', true);
3150
+ animatedObjects.push('theta');
3151
+ break;
3152
+ default:
3153
+ throw new Error(`Unknown demo type: ${demoType}`);
3154
+ }
3155
+ // Set up coordinate system for better viewing
3156
+ await instance.setCoordSystem(-6, 6, -6, 6);
3157
+ await instance.setAxesVisible(true, true);
3158
+ await instance.setGridVisible(true);
3159
+ return {
3160
+ content: [{
3161
+ type: 'text',
3162
+ text: JSON.stringify({
3163
+ success: true,
3164
+ demo: {
3165
+ type: demoType,
3166
+ animationSpeed,
3167
+ createdObjects,
3168
+ animatedObjects,
3169
+ message: `Animation demo '${demoType}' created successfully!`,
3170
+ instructions: [
3171
+ 'Use geogebra_start_animation to begin the animation',
3172
+ 'Use geogebra_stop_animation to pause it',
3173
+ 'Use geogebra_animation_status to check if it\'s running',
3174
+ 'Use geogebra_export_animation to capture frames for GIF creation',
3175
+ 'Objects with tracing enabled will show their paths during animation'
3176
+ ]
3177
+ }
3178
+ }, null, 2)
3179
+ }]
3180
+ };
3181
+ }
3182
+ catch (error) {
3183
+ logger_1.default.error('Failed to create animation demo', error);
3184
+ return {
3185
+ content: [{
3186
+ type: 'text',
3187
+ text: JSON.stringify({
3188
+ success: false,
3189
+ error: error instanceof Error ? error.message : String(error)
3190
+ }, null, 2)
3191
+ }],
3192
+ isError: true
3193
+ };
3194
+ }
3195
+ }
3196
+ },
3197
+ // Missing Tools Implementation
3198
+ {
3199
+ tool: {
3200
+ name: 'geogebra_create_line_segment',
3201
+ description: 'Create a line segment in GeoGebra between two points',
3202
+ inputSchema: {
3203
+ type: 'object',
3204
+ properties: {
3205
+ name: {
3206
+ type: 'string',
3207
+ description: 'Name of the line segment (e.g., "AB", "segment1")'
3208
+ },
3209
+ point1: {
3210
+ type: 'string',
3211
+ description: 'Name of the first point'
3212
+ },
3213
+ point2: {
3214
+ type: 'string',
3215
+ description: 'Name of the second point'
3216
+ },
3217
+ color: {
3218
+ type: 'string',
3219
+ description: 'Color of the line segment (hex format, e.g., "#FF0000")'
3220
+ },
3221
+ thickness: {
3222
+ type: 'number',
3223
+ description: 'Thickness of the line segment (1-10)',
3224
+ minimum: 1,
3225
+ maximum: 10
3226
+ },
3227
+ style: {
3228
+ type: 'string',
3229
+ enum: ['solid', 'dashed', 'dotted'],
3230
+ description: 'Line style (default: solid)'
3231
+ }
3232
+ },
3233
+ required: ['name', 'point1', 'point2']
3234
+ }
3235
+ },
3236
+ handler: async (params) => {
3237
+ try {
3238
+ const name = params['name'];
3239
+ const point1 = params['point1'];
3240
+ const point2 = params['point2'];
3241
+ const color = params['color'];
3242
+ const thickness = params['thickness'];
3243
+ const style = params['style'];
3244
+ // Validate segment name
3245
+ const nameValidation = (0, validation_1.validateObjectName)(name);
3246
+ if (!nameValidation.isValid) {
3247
+ throw new Error(`Invalid segment name: ${nameValidation.error}`);
3248
+ }
3249
+ // Validate point names
3250
+ const point1Validation = (0, validation_1.validateObjectName)(point1);
3251
+ if (!point1Validation.isValid) {
3252
+ throw new Error(`Invalid first point name: ${point1Validation.error}`);
3253
+ }
3254
+ const point2Validation = (0, validation_1.validateObjectName)(point2);
3255
+ if (!point2Validation.isValid) {
3256
+ throw new Error(`Invalid second point name: ${point2Validation.error}`);
3257
+ }
3258
+ const instance = await exports.instancePool.getDefaultInstance();
3259
+ // Create the line segment
3260
+ const command = `${name} = Segment(${point1}, ${point2})`;
3261
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
3262
+ if (!result.success) {
3263
+ throw new Error(result.error || 'Failed to create line segment');
3264
+ }
3265
+ // Apply styling if provided
3266
+ const styleCommands = [];
3267
+ if (color) {
3268
+ styleCommands.push(`SetColor(${name}, "${color}")`);
3269
+ }
3270
+ if (thickness) {
3271
+ styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
3272
+ }
3273
+ if (style && style !== 'solid') {
3274
+ const lineType = style === 'dashed' ? '10' : '20';
3275
+ styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
3276
+ }
3277
+ // Execute styling commands
3278
+ for (const styleCmd of styleCommands) {
3279
+ await instance.evalCommand(styleCmd);
3280
+ }
3281
+ const segmentInfo = await instance.getObjectInfo(name);
3282
+ return {
3283
+ content: [{
3284
+ type: 'text',
3285
+ text: JSON.stringify({
3286
+ success: true,
3287
+ command,
3288
+ segment: segmentInfo,
3289
+ styling: {
3290
+ color,
3291
+ thickness,
3292
+ style
3293
+ }
3294
+ }, null, 2)
3295
+ }]
3296
+ };
3297
+ }
3298
+ catch (error) {
3299
+ logger_1.default.error('Failed to create line segment', error);
3300
+ return {
3301
+ content: [{
3302
+ type: 'text',
3303
+ text: JSON.stringify({
3304
+ success: false,
3305
+ error: error instanceof Error ? error.message : String(error)
3306
+ }, null, 2)
3307
+ }],
3308
+ isError: true
3309
+ };
3310
+ }
3311
+ }
3312
+ },
3313
+ {
3314
+ tool: {
3315
+ name: 'geogebra_create_text',
3316
+ description: 'Create a text object in GeoGebra with specified content and position',
3317
+ inputSchema: {
3318
+ type: 'object',
3319
+ properties: {
3320
+ text: {
3321
+ type: 'string',
3322
+ description: 'Text content to display (can include GeoGebra expressions in quotes)'
3323
+ },
3324
+ x: {
3325
+ type: 'number',
3326
+ description: 'X coordinate for text position'
3327
+ },
3328
+ y: {
3329
+ type: 'number',
3330
+ description: 'Y coordinate for text position'
3331
+ },
3332
+ fontSize: {
3333
+ type: 'number',
3334
+ description: 'Font size in points (default: 12)',
3335
+ minimum: 8,
3336
+ maximum: 72
3337
+ },
3338
+ color: {
3339
+ type: 'string',
3340
+ description: 'Text color (hex format, e.g., "#000000")'
3341
+ },
3342
+ fontStyle: {
3343
+ type: 'string',
3344
+ enum: ['normal', 'bold', 'italic'],
3345
+ description: 'Font style (default: normal)'
3346
+ },
3347
+ name: {
3348
+ type: 'string',
3349
+ description: 'Optional name for the text object (auto-generated if not provided)'
3350
+ }
3351
+ },
3352
+ required: ['text', 'x', 'y']
3353
+ }
3354
+ },
3355
+ handler: async (params) => {
3356
+ try {
3357
+ const text = params['text'];
3358
+ const x = params['x'];
3359
+ const y = params['y'];
3360
+ const fontSize = params['fontSize'] || 12;
3361
+ const color = params['color'];
3362
+ const fontStyle = params['fontStyle'] || 'normal';
3363
+ const name = params['name'] || `text_${Date.now()}`;
3364
+ // Validate text object name
3365
+ const nameValidation = (0, validation_1.validateObjectName)(name);
3366
+ if (!nameValidation.isValid) {
3367
+ throw new Error(`Invalid text name: ${nameValidation.error}`);
3368
+ }
3369
+ // Validate coordinates
3370
+ const coordValidation = (0, validation_1.validateCoordinates)(x, y);
3371
+ if (!coordValidation.isValid) {
3372
+ throw new Error(`Invalid coordinates: ${coordValidation.error}`);
3373
+ }
3374
+ const instance = await exports.instancePool.getDefaultInstance();
3375
+ // Format the text content properly for GeoGebra
3376
+ let formattedText;
3377
+ // If text is already quoted or contains dynamic expressions, use as-is
3378
+ if ((text.startsWith('"') && text.endsWith('"')) || text.includes(' + ')) {
3379
+ formattedText = text;
3380
+ }
3381
+ else {
3382
+ // Otherwise, wrap static text in quotes
3383
+ formattedText = `"${text}"`;
3384
+ }
3385
+ // Create the text object
3386
+ const command = `${name} = Text(${formattedText}, (${x}, ${y}))`;
3387
+ const result = await evalCommandWithLatexCaptionDefaults(instance, command);
3388
+ if (!result.success) {
3389
+ throw new Error(result.error || 'Failed to create text');
3390
+ }
3391
+ // Apply styling if provided
3392
+ const styleCommands = [];
3393
+ if (color) {
3394
+ styleCommands.push(`SetColor(${name}, "${color}")`);
3395
+ }
3396
+ if (fontSize !== 12) {
3397
+ styleCommands.push(`SetTextSize(${name}, ${fontSize})`);
3398
+ }
3399
+ if (fontStyle !== 'normal') {
3400
+ const styleNumber = fontStyle === 'bold' ? '1' : fontStyle === 'italic' ? '2' : '0';
3401
+ styleCommands.push(`SetTextStyle(${name}, ${styleNumber})`);
3402
+ }
3403
+ // Execute styling commands
3404
+ for (const styleCmd of styleCommands) {
3405
+ await instance.evalCommand(styleCmd);
3406
+ }
3407
+ const textInfo = await instance.getObjectInfo(name);
3408
+ return {
3409
+ content: [{
3410
+ type: 'text',
3411
+ text: JSON.stringify({
3412
+ success: true,
3413
+ command,
3414
+ textObject: textInfo,
3415
+ position: { x, y },
3416
+ styling: {
3417
+ fontSize,
3418
+ color,
3419
+ fontStyle
3420
+ }
3421
+ }, null, 2)
3422
+ }]
3423
+ };
3424
+ }
3425
+ catch (error) {
3426
+ logger_1.default.error('Failed to create text', error);
3427
+ return {
3428
+ content: [{
3429
+ type: 'text',
3430
+ text: JSON.stringify({
3431
+ success: false,
3432
+ error: error instanceof Error ? error.message : String(error)
3433
+ }, null, 2)
3434
+ }],
3435
+ isError: true
3436
+ };
3437
+ }
3438
+ }
3439
+ }
3440
+ ];
3441
+ //# sourceMappingURL=geogebra-tools.js.map