enotkrutoy commited on
Commit
165912b
·
verified ·
1 Parent(s): f60659c

Upload 35 files

Browse files
Files changed (35) hide show
  1. FastMCPToolGenerator/.gitattributes +35 -0
  2. FastMCPToolGenerator/.gitignore +63 -0
  3. FastMCPToolGenerator/App.tsx +283 -0
  4. FastMCPToolGenerator/Dockerfile +77 -0
  5. FastMCPToolGenerator/GEMINI.md +50 -0
  6. FastMCPToolGenerator/README0.md +20 -0
  7. FastMCPToolGenerator/backend/__pycache__/main.cpython-313.pyc +0 -0
  8. FastMCPToolGenerator/backend/main.py +164 -0
  9. FastMCPToolGenerator/components/AiPrompt.tsx +52 -0
  10. FastMCPToolGenerator/components/CodeOutput.tsx +340 -0
  11. FastMCPToolGenerator/components/FunctionEditor.tsx +146 -0
  12. FastMCPToolGenerator/components/ToolForm.tsx +115 -0
  13. FastMCPToolGenerator/components/ui/Button.tsx +28 -0
  14. FastMCPToolGenerator/components/ui/Card.tsx +21 -0
  15. FastMCPToolGenerator/components/ui/Input.tsx +19 -0
  16. FastMCPToolGenerator/components/ui/Label.tsx +16 -0
  17. FastMCPToolGenerator/components/ui/Select.tsx +28 -0
  18. FastMCPToolGenerator/components/ui/Textarea.tsx +20 -0
  19. FastMCPToolGenerator/constants.ts +18 -0
  20. FastMCPToolGenerator/dist/assets/index-CjZgBbLF.js +0 -0
  21. FastMCPToolGenerator/dist/index.html +46 -0
  22. FastMCPToolGenerator/examples.ts +60 -0
  23. FastMCPToolGenerator/index.html +45 -0
  24. FastMCPToolGenerator/index.tsx +20 -0
  25. FastMCPToolGenerator/metadata.json +7 -0
  26. FastMCPToolGenerator/package-lock.json +0 -0
  27. FastMCPToolGenerator/package.json +42 -0
  28. FastMCPToolGenerator/requirements.txt +11 -0
  29. FastMCPToolGenerator/services/aiService.ts +71 -0
  30. FastMCPToolGenerator/services/codeGenerator.ts +428 -0
  31. FastMCPToolGenerator/style.css +28 -0
  32. FastMCPToolGenerator/tsconfig.json +32 -0
  33. FastMCPToolGenerator/types.ts +72 -0
  34. FastMCPToolGenerator/vite.config.ts +72 -0
  35. README.md +20 -10
FastMCPToolGenerator/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
FastMCPToolGenerator/.gitignore ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ # Logs
3
+
4
+ logs
5
+
6
+ *.log
7
+
8
+ npm-debug.log*
9
+
10
+ yarn-debug.log*
11
+
12
+ yarn-error.log*
13
+
14
+ pnpm-debug.log*
15
+
16
+ lerna-debug.log*
17
+
18
+
19
+
20
+ node_modules
21
+
22
+ dist
23
+
24
+ dist-ssr
25
+
26
+ *.local
27
+
28
+ .env
29
+
30
+
31
+
32
+ # Editor directories and files
33
+
34
+ .vscode/*
35
+
36
+ !.vscode/extensions.json
37
+
38
+ .idea
39
+
40
+ .DS_Store
41
+
42
+ *.suo
43
+
44
+ *.ntvs*
45
+
46
+ *.njsproj
47
+
48
+ *.sln
49
+
50
+ *.sw?
51
+
52
+
53
+
54
+ # Python
55
+
56
+ __pycache__/
57
+
58
+ *.pyc
59
+
60
+ .venv
61
+
62
+ venv/
63
+
FastMCPToolGenerator/App.tsx ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useMemo } from 'react';
3
+
4
+ import { ToolDefinition, GeneratedCode, ToolType, FunctionDefinition, ParameterType } from './types';
5
+
6
+ import ToolForm from './components/ToolForm';
7
+
8
+ import CodeOutput from './components/CodeOutput';
9
+
10
+ import { generateAllCode } from './services/codeGenerator';
11
+
12
+ import { nmapScannerExample } from './examples';
13
+
14
+ import Button from './components/ui/Button';
15
+
16
+ import AiPrompt from './components/AiPrompt';
17
+
18
+ import { generateToolDefinitionFromPrompt } from './services/aiService';
19
+
20
+
21
+
22
+ const initialFunction: FunctionDefinition = {
23
+
24
+ id: crypto.randomUUID(),
25
+
26
+ name: 'example_function',
27
+
28
+ description: 'Краткое описание того, что делает эта функция.',
29
+
30
+ parameters: [
31
+
32
+ {
33
+
34
+ id: crypto.randomUUID(),
35
+
36
+ name: 'example_param',
37
+
38
+ type: ParameterType.STRING, // Исправлено с 'str' as any
39
+
40
+ description: 'Описание параметра.',
41
+
42
+ required: true,
43
+
44
+ },
45
+
46
+ ],
47
+
48
+ returnType: ParameterType.STRING, // Исправлено с 'str' as any
49
+
50
+ returnDescription: 'Описание возвращаемого значения.',
51
+
52
+ };
53
+
54
+
55
+
56
+ const initialToolDefinition: ToolDefinition = {
57
+
58
+ name: 'my_tool',
59
+
60
+ type: ToolType.UTILITY,
61
+
62
+ dependencies: '',
63
+
64
+ functions: [initialFunction],
65
+
66
+ };
67
+
68
+
69
+
70
+ const App: React.FC = () => {
71
+
72
+ const [toolDefinition, setToolDefinition] = useState<ToolDefinition>(initialToolDefinition);
73
+
74
+ const [aiPrompt, setAiPrompt] = useState<string>('');
75
+
76
+ const [isGenerating, setIsGenerating] = useState<boolean>(false);
77
+
78
+ const [aiError, setAiError] = useState<string | null>(null);
79
+
80
+
81
+
82
+ const generatedCode = useMemo<GeneratedCode | null>(() => {
83
+
84
+ try {
85
+
86
+ if (toolDefinition.name && toolDefinition.functions.length > 0 && toolDefinition.functions.every(f => f.name)) {
87
+
88
+ return generateAllCode(toolDefinition);
89
+
90
+ }
91
+
92
+ return null;
93
+
94
+ } catch (error) {
95
+
96
+ console.error("Error generating code:", error);
97
+
98
+ return null;
99
+
100
+ }
101
+
102
+ }, [toolDefinition]);
103
+
104
+
105
+
106
+ const loadNmapExample = () => {
107
+
108
+ setToolDefinition(nmapScannerExample);
109
+
110
+ };
111
+
112
+
113
+
114
+ const handleAiGenerate = async () => {
115
+
116
+ if (!aiPrompt.trim()) {
117
+
118
+ setAiError("Пожалуйста, введите описание инструмента.");
119
+
120
+ return;
121
+
122
+ }
123
+
124
+ setIsGenerating(true);
125
+
126
+ setAiError(null);
127
+
128
+ try {
129
+
130
+ const jsonString = await generateToolDefinitionFromPrompt(aiPrompt);
131
+
132
+
133
+
134
+ // Бэкенд уже должен был очистить markdown, но на всякий случай
135
+
136
+ const cleanedJsonString = jsonString.replace(/^```json\s*|\s*```$/g, '').trim();
137
+
138
+ const newToolDefinition = JSON.parse(cleanedJsonString);
139
+
140
+
141
+
142
+ // Добавим уникальные ID, так как AI их не генерирует
143
+
144
+ if (newToolDefinition.functions) {
145
+
146
+ newToolDefinition.functions.forEach((func: FunctionDefinition) => {
147
+
148
+ func.id = crypto.randomUUID();
149
+
150
+ if (func.parameters) {
151
+
152
+ func.parameters.forEach((param: any) => {
153
+
154
+ param.id = crypto.randomUUID();
155
+
156
+ });
157
+
158
+ } else {
159
+
160
+ func.parameters = []; // Убедимся, что параметры существуют
161
+
162
+ }
163
+
164
+ });
165
+
166
+ } else {
167
+
168
+ newToolDefinition.functions = []; // Убедимся, что функции существуют
169
+
170
+ }
171
+
172
+
173
+
174
+ setToolDefinition(newToolDefinition);
175
+
176
+
177
+
178
+ } catch (error) {
179
+
180
+ console.error("AI generation failed:", error);
181
+
182
+ setAiError(`Не удалось сгенерировать инструмент: ${error.message}. Проверьте консоль.`);
183
+
184
+ } finally {
185
+
186
+ setIsGenerating(false);
187
+
188
+ }
189
+
190
+ };
191
+
192
+
193
+
194
+ return (
195
+
196
+ <div className="min-h-screen bg-gray-900 text-gray-100 p-4 sm:p-6 lg:p-8">
197
+
198
+ <div className="max-w-screen-2xl mx-auto">
199
+
200
+ <header className="mb-8">
201
+
202
+ <div className="text-center">
203
+
204
+ <h1 className="text-4xl sm:text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-sky-400 to-blue-600">
205
+
206
+ Gemini CLI Tool Generator
207
+
208
+ </h1>
209
+
210
+ <p className="mt-2 text-lg text-gray-400">
211
+
212
+ Создайте кастомные расширения, которые Gemini сможет вызывать для решения ваших задач.
213
+
214
+ </p>
215
+
216
+ </div>
217
+
218
+ <div className="text-center mt-4">
219
+
220
+ <Button onClick={loadNmapExample} variant="secondary">
221
+
222
+ Загрузить пример: Nmap Scanner
223
+
224
+ </Button>
225
+
226
+ </div>
227
+
228
+ </header>
229
+
230
+
231
+
232
+ <AiPrompt
233
+
234
+ prompt={aiPrompt}
235
+
236
+ setPrompt={setAiPrompt}
237
+
238
+ onGenerate={handleAiGenerate}
239
+
240
+ isLoading={isGenerating}
241
+
242
+ error={aiError}
243
+
244
+ />
245
+
246
+
247
+
248
+ <main className="grid grid-cols-1 xl:grid-cols-2 gap-8 mt-8">
249
+
250
+ <ToolForm toolDefinition={toolDefinition} setToolDefinition={setToolDefinition} />
251
+
252
+ <CodeOutput generatedCode={generatedCode} toolName={toolDefinition.name} />
253
+
254
+ </main>
255
+
256
+
257
+
258
+ <footer className="text-center mt-12 py-4 border-t border-gray-700">
259
+
260
+ <p className="text-gray-500">
261
+
262
+ Разработано с использованием React, Tailwind CSS и Gemini.
263
+
264
+ </p>
265
+
266
+ </footer>
267
+
268
+ </div>
269
+
270
+ </div>
271
+
272
+ );
273
+
274
+ };
275
+
276
+
277
+
278
+ export default App;
279
+
280
+
281
+
282
+
283
+
FastMCPToolGenerator/Dockerfile ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ # Этап 1: Сборка React-фронтенда
3
+
4
+ FROM node:18-slim AS build
5
+
6
+ WORKDIR /app
7
+
8
+
9
+
10
+ # Копируем package.json и устанавливаем npm зависимости
11
+
12
+ COPY package.json package-lock.json ./
13
+
14
+ RUN npm install
15
+
16
+
17
+
18
+ # Копируем остальной код фронтенда
19
+
20
+ COPY . .
21
+
22
+
23
+
24
+ # Собираем production build (создает папку 'dist')
25
+
26
+ RUN npm run build
27
+
28
+
29
+
30
+ # Этап 2: Настройка Python-сервера (FastAPI)
31
+
32
+ FROM python:3.10-slim
33
+
34
+ WORKDIR /app
35
+
36
+
37
+
38
+ # Установка переменных окружения
39
+
40
+ ENV PYTHONUNBUFFERED 1
41
+
42
+ ENV PYTHONDONTWRITEBYTECODE 1
43
+
44
+
45
+
46
+ # Копируем Python-зависимости и устанавливаем их
47
+
48
+ COPY requirements.txt .
49
+
50
+ RUN pip install --no-cache-dir -r requirements.txt
51
+
52
+
53
+
54
+ # Копируем код бэкенда (FastAPI)
55
+
56
+ COPY ./backend ./backend
57
+
58
+
59
+
60
+ # Копируем собранный фронтенд из Этапа 1
61
+
62
+ COPY --from=build /app/dist ./dist
63
+
64
+
65
+
66
+ # Порт 7860 - стандартный для HF Spaces
67
+
68
+ EXPOSE 7860
69
+
70
+ ENV PORT=7860
71
+
72
+
73
+
74
+ # Команда для запуска Uvicorn (FastAPI)
75
+
76
+ CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
77
+
FastMCPToolGenerator/GEMINI.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Инструкция для Агента: Инструмент "nmap_scanner"
2
+
3
+ ## Описание
4
+
5
+ Этот инструмент предоставляет прямой доступ к сетевому сканеру Nmap для проведения разведки и обнаружения хостов в сети. Он относится к типу **UTILITY**. Используй его для анализа сетевой безопасности, инвентаризации устройств и проверки открытых портов.
6
+
7
+ ## Функции
8
+
9
+ ### `nmap_scanner:run_scan`
10
+
11
+ Запускает детальное сканирование Nmap на указанные цели. Позволяет гибко настраивать тип сканирования, определять службы и версии ПО.
12
+
13
+ **Параметры:**
14
+ - `targets` (*str*): Цели для сканирования (IP-адрес, домен, CIDR-диапазон). Например: '192.168.1.1', 'scanme.nmap.org', '10.0.0.0/24'. (обязательный)
15
+ - `ports` (*str*): Порты для сканирования. Например: '21-25,80,443', '1-1024'. (обязательный)
16
+ - `arguments` (*str*): Дополнительные аргументы командной строки Nmap. Например: '-sV -sC' для определения версий и запуска скриптов, или '-A' для агрессивного сканирования. (обязательный)
17
+
18
+ **Возвращает:**
19
+ *dict* - Результат сканирования в формате JSON, содержащий информацию о хостах, их состоянии, открытых портах, службах и версиях.
20
+
21
+ ---
22
+
23
+ ### `nmap_scanner:host_discovery`
24
+
25
+ Быстро обнаруживает активные (включенные) хосты в указанной сети с помощью 'ping scan' (`-sn`), не проводя полного сканирования портов.
26
+
27
+ **Параметры:**
28
+ - `network_cidr` (*str*): Сетевой диапазон в формате CIDR для обнаружения хостов. Например: '192.168.1.0/24'. (обязательный)
29
+
30
+ **Возвращает:**
31
+ *list* - Список IP-адресов активных хостов, обнаруженных в сети.
32
+
33
+ ## Примеры использования
34
+
35
+ **ВАЖНО:** Всегда используй формат `nmap_scanner:имя_функции()`.
36
+
37
+ **Пример 1: Обнаружение всех активных устройств в локальной сети**
38
+ ```
39
+ nmap_scanner:host_discovery(network_cidr="192.168.0.0/24")
40
+ ```
41
+
42
+ **Пример 2: Быстрое сканирование популярных портов на конкретном хосте**
43
+ ```
44
+ nmap_scanner:run_scan(targets="192.168.1.1", ports="1-1024", arguments="-T4")
45
+ ```
46
+
47
+ **Пример 3: Агрессивное сканирование веб-сервера для определения ОС, служб и уязвимостей**
48
+ ```
49
+ nmap_scanner:run_scan(targets="scanme.nmap.org", ports="80,443", arguments="-A -v")
50
+ ```
FastMCPToolGenerator/README0.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
3
+ </div>
4
+
5
+ # Run and deploy your AI Studio app
6
+
7
+ This contains everything you need to run your app locally.
8
+
9
+ View your app in AI Studio: https://ai.studio/apps/drive/1DWLDNsbZ8bO_lQPzuDVQB3cpW92HgYCC
10
+
11
+ ## Run Locally
12
+
13
+ **Prerequisites:** Node.js
14
+
15
+
16
+ 1. Install dependencies:
17
+ `npm install`
18
+ 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
19
+ 3. Run the app:
20
+ `npm run dev`
FastMCPToolGenerator/backend/__pycache__/main.cpython-313.pyc ADDED
Binary file (6.89 kB). View file
 
FastMCPToolGenerator/backend/main.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.responses import FileResponse
5
+ from fastapi.staticfiles import StaticFiles
6
+ from pydantic import BaseModel
7
+ import google.generativeai as genai
8
+ from google.generativeai.types import HarmCategory, HarmBlockThreshold
9
+
10
+ # Получаем ключ из переменных окружения (HF Secrets)
11
+ API_KEY = os.environ.get("GEMINI_API_KEY")
12
+
13
+ if not API_KEY:
14
+ print("ВНИМАНИЕ: GEMINI_API_KEY не установлен в переменных окружения.")
15
+ else:
16
+ genai.configure(api_key=API_KEY)
17
+
18
+ app = FastAPI()
19
+
20
+ # Модель для входящего запроса от фронтенда
21
+ class PromptRequest(BaseModel):
22
+ prompt: str
23
+
24
+ def get_ai_prompt(user_prompt: str):
25
+ return f"""
26
+ Вы — эксперт по генерации JSON-определений инструментов (ToolDefinition) для FastMCP (Gemini CLI).
27
+ Ваша задача — принять текстовое описание инструмента от пользователя и вернуть ТОЛЬКО JSON-структуру, соответствующую интерфейсу ToolDefinition.
28
+
29
+ Интерфейс ToolDefinition (TypeScript):
30
+ export enum ToolType {{
31
+ REST_API = 'REST_API',
32
+ CLI = 'CLI',
33
+ DATABASE = 'DATABASE',
34
+ CUSTOM = 'CUSTOM',
35
+ UTILITY = 'UTILITY',
36
+ CALCULATOR = 'CALCULATOR',
37
+ CONVERTER = 'CONVERTER'
38
+ }}
39
+ export enum ParameterType {{
40
+ STRING = 'str',
41
+ INTEGER = 'int',
42
+ FLOAT = 'float',
43
+ BOOLEAN = 'bool',
44
+ LIST = 'list',
45
+ DICTIONARY = 'dict'
46
+ }}
47
+ export interface ParameterDefinition {{
48
+ name: string;
49
+ type: ParameterType;
50
+ description: string;
51
+ required: boolean;
52
+ }}
53
+ export interface FunctionDefinition {{
54
+ name: string;
55
+ description: string;
56
+ parameters: ParameterDefinition[];
57
+ returnType: ParameterType;
58
+ returnDescription: string;
59
+ }}
60
+ export interface ToolDefinition {{
61
+ name: string;
62
+ type: ToolType;
63
+ dependencies: string;
64
+ functions: FunctionDefinition[];
65
+ }}
66
+
67
+ Правила:
68
+ 1. НЕ добавляйте ID. Фронтенд сделает это.
69
+ 2. Возвращайте ТОЛЬКО JSON-код. Без markdown, без "вот ваш JSON".
70
+ 3. Имена функций и параметров должны быть в snake_case.
71
+ 4. Тип (ToolType) должен быть наиболее подходящим (часто это UTILITY или CLI).
72
+ 5. Dependencies (зависимости) должны быть пустыми (""), если не указано иное (например, для 'nmap' нужна 'python-nmap').
73
+
74
+ Пример запроса пользователя:
75
+ "Инструмент для получения текущей погоды в городе."
76
+
77
+ Пример вашего ИДЕАЛЬНОГО ответа (ТОЛЬКО JSON):
78
+ {{
79
+ "name": "weather_tool",
80
+ "type": "REST_API",
81
+ "dependencies": "requests",
82
+ "functions": [
83
+ {{
84
+ "name": "get_current_weather",
85
+ "description": "Получает текущую погоду для указанного города.",
86
+ "parameters": [
87
+ {{
88
+ "name": "city_name",
89
+ "type": "str",
90
+ "description": "Название города, для которого нужно получить погоду.",
91
+ "required": true
92
+ }}
93
+ ],
94
+ "returnType": "dict",
95
+ "returnDescription": "Словарь с данными о погоде (температура, влажность и т.д.)."
96
+ }}
97
+ ]
98
+ }}
99
+ ---
100
+ ЗАПРОС ПОЛЬЗОВАТЕЛЯ:
101
+ "{user_prompt}"
102
+
103
+ ВАШ ОТВЕТ (ТОЛЬКО JSON):
104
+ """
105
+
106
+ generation_config = {
107
+ "temperature": 0.7,
108
+ "top_p": 1.0,
109
+ "top_k": 32,
110
+ "max_output_tokens": 4096,
111
+ }
112
+
113
+ safety_settings = [
114
+ {"category": HarmCategory.HARM_CATEGORY_HARASSMENT, "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE},
115
+ {"category": HarmCategory.HARM_CATEGORY_HATE_SPEECH, "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE},
116
+ {"category": HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE},
117
+ {"category": HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE},
118
+ ]
119
+
120
+ @app.post("/api/generate")
121
+ async def handle_generate(request: PromptRequest):
122
+ if not API_KEY:
123
+ raise HTTPException(status_code=500, detail="GEMINI_API_KEY не настроен на сервере.")
124
+
125
+ try:
126
+ model = genai.GenerativeModel(
127
+ model_name="gemini-1.5-pro-latest",
128
+ generation_config=generation_config,
129
+ safety_settings=safety_settings
130
+ )
131
+
132
+ prompt_parts = [get_ai_prompt(request.prompt)]
133
+ response = model.generate_content(prompt_parts)
134
+
135
+ json_string = response.text
136
+
137
+ cleaned_json_string = re.sub(r'^```json\s*|\s*```$', '', json_string, flags=re.MULTILINE).strip()
138
+
139
+ return {"jsonString": cleaned_json_string}
140
+
141
+ except Exception as e:
142
+ print(f"Ошибка вызова Gemini API: {e}")
143
+ raise HTTPException(status_code=500, detail=f"Ошибка генерации AI: {str(e)}")
144
+
145
+ # --- Настройки для развертывания на Hugging Face Spaces/Docker ---
146
+ # PORT=7860 устанавливается Hugging Face при запуске контейнера
147
+ IS_HUGGINGFACE_SPACE = os.environ.get("PORT") == "7860"
148
+
149
+ if IS_HUGGINGFACE_SPACE:
150
+
151
+ # Монтируем статические файлы (React build)
152
+ app.mount("/assets", StaticFiles(directory="/app/dist/assets"), name="assets")
153
+
154
+ @app.get("/{full_path:path}")
155
+ async def serve_react_app(full_path: str):
156
+ # Обслуживаем index.html для всех non-API маршрутов
157
+ return FileResponse("/app/dist/index.html")
158
+
159
+ else:
160
+ # При локальной разработке (Vite) FastAPI отвечает ТОЛЬКО за API.
161
+ # Статику раздает Vite (порт 3000).
162
+ print("ℹ️ Режим локальной разработки. Статические файлы обслуживаются Vite.")
163
+
164
+
FastMCPToolGenerator/components/AiPrompt.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import Card from './ui/Card';
4
+ import Textarea from './ui/Textarea';
5
+ import Button from './ui/Button';
6
+ import Label from './ui/Label';
7
+
8
+ interface AiPromptProps {
9
+ prompt: string;
10
+ setPrompt: (value: string) => void;
11
+ onGenerate: () => void;
12
+ isLoading: boolean;
13
+ error: string | null;
14
+ }
15
+
16
+ const AiPrompt: React.FC<AiPromptProps> = ({ prompt, setPrompt, onGenerate, isLoading, error }) => {
17
+ return (
18
+ <Card className="mb-8 border-sky-500 border-2 shadow-lg shadow-sky-500/10">
19
+ <h2 className="text-2xl font-bold mb-4 text-sky-400">
20
+ ✨ Создание с помощью AI
21
+ </h2>
22
+ <p className="text-gray-400 mb-4 text-sm">
23
+ Опишите инструмент, который вы хотите создать, на естественном языке. AI проанализирует ваш запрос и автоматически заполнит форму ниже.
24
+ </p>
25
+
26
+ <div>
27
+ <Label htmlFor="ai-prompt">Ваш запрос:</Label>
28
+ <Textarea
29
+ id="ai-prompt"
30
+ value={prompt}
31
+ onChange={(e) => setPrompt(e.target.value)}
32
+ placeholder="Например: 'Инструмент для получения текущей погоды в городе' или 'Утилита для работы с файлами: чтение, запись и удаление'"
33
+ rows={3}
34
+ disabled={isLoading}
35
+ />
36
+ </div>
37
+
38
+ <div className="mt-4 flex items-center justify-between">
39
+ <Button onClick={onGenerate} disabled={isLoading}>
40
+ {isLoading ? 'Генерация...' : 'Сгенерировать'}
41
+ </Button>
42
+ {error && <p className="text-red-400 text-sm ml-4">{error}</p>}
43
+ </div>
44
+ </Card>
45
+ );
46
+ };
47
+
48
+ export default AiPrompt;
49
+
50
+
51
+
52
+
FastMCPToolGenerator/components/CodeOutput.tsx ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+
3
+ import { GeneratedCode } from '../types';
4
+
5
+ import JSZip from 'jszip'; // Импортируем JSZip из npm
6
+
7
+
8
+
9
+ interface CodeOutputProps {
10
+
11
+ generatedCode: GeneratedCode | null;
12
+
13
+ toolName: string;
14
+
15
+ }
16
+
17
+
18
+
19
+ type Tab = 'setup.py' | 'server.py' | '.gemini/settings.json' | 'GEMINI.md' | 'Инструкция' | 'Примеры';
20
+
21
+
22
+
23
+ const CodeOutput: React.FC<CodeOutputProps> = ({ generatedCode, toolName }) => {
24
+
25
+ const [activeTab, setActiveTab] = useState<Tab>('Примеры');
26
+
27
+ const [copiedTab, setCopiedTab] = useState<Tab | null>(null);
28
+
29
+
30
+
31
+ const handleCopy = (content: string, tab: Tab) => {
32
+
33
+ navigator.clipboard.writeText(content);
34
+
35
+ setCopiedTab(tab);
36
+
37
+ setTimeout(() => setCopiedTab(null), 2000);
38
+
39
+ };
40
+
41
+
42
+
43
+ const handleDownloadZip = () => {
44
+
45
+ if (!generatedCode || !toolName) return;
46
+
47
+
48
+
49
+ const zip = new JSZip(); // Теперь JSZip импортирован
50
+
51
+ const rootFolder = zip.folder(toolName);
52
+
53
+ if (!rootFolder) return;
54
+
55
+
56
+
57
+ rootFolder.file('setup.py', generatedCode.setupPy);
58
+
59
+ rootFolder.file('server.py', generatedCode.serverPy);
60
+
61
+ rootFolder.file('GEMINI.md', generatedCode.geminiMd);
62
+
63
+
64
+
65
+ const geminiFolder = rootFolder.folder('.gemini');
66
+
67
+ if (geminiFolder) {
68
+
69
+ geminiFolder.file('settings.json', generatedCode.settingsJson);
70
+
71
+ }
72
+
73
+
74
+
75
+ zip.generateAsync({ type: 'blob' }).then(content => {
76
+
77
+ const link = document.createElement('a');
78
+
79
+ link.href = URL.createObjectURL(content);
80
+
81
+ link.download = `${toolName}.zip`;
82
+
83
+ document.body.appendChild(link);
84
+
85
+ link.click();
86
+
87
+ document.body.removeChild(link);
88
+
89
+ });
90
+
91
+ };
92
+
93
+
94
+
95
+ const tabs: Tab[] = ['Примеры', 'GEMINI.md', 'server.py', 'setup.py', '.gemini/settings.json', 'Инструкция'];
96
+
97
+
98
+
99
+ const getContentForTab = (tab: Tab): string => {
100
+
101
+ if (!generatedCode) return "Сгенерируйте код, заполнив форму слева.";
102
+
103
+ switch (tab) {
104
+
105
+ case 'setup.py': return generatedCode.setupPy;
106
+
107
+ case 'server.py': return generatedCode.serverPy;
108
+
109
+ case '.gemini/settings.json': return generatedCode.settingsJson;
110
+
111
+ case 'GEMINI.md': return generatedCode.geminiMd;
112
+
113
+ case 'Примеры': return generatedCode.examplesMd;
114
+
115
+ case 'Инструкция': return getInstructions(toolName, generatedCode);
116
+
117
+ default: return '';
118
+
119
+ }
120
+
121
+ };
122
+
123
+
124
+
125
+ const getLanguageForTab = (tab: Tab): string => {
126
+
127
+ switch(tab) {
128
+
129
+ case 'setup.py':
130
+
131
+ case 'server.py':
132
+
133
+ return 'python';
134
+
135
+ case '.gemini/settings.json':
136
+
137
+ return 'json';
138
+
139
+ case 'GEMINI.md':
140
+
141
+ case 'Инструкция':
142
+
143
+ case 'Примеры':
144
+
145
+ return 'markdown';
146
+
147
+ default:
148
+
149
+ return 'plaintext';
150
+
151
+ }
152
+
153
+ }
154
+
155
+
156
+
157
+ const getInstructions = (toolName: string, code: GeneratedCode): string => {
158
+
159
+ const primaryFunctionMatch = code.serverPy?.match(/def\s+([a-zA-Z0-9_]+)\(/);
160
+
161
+ const primaryFunction = primaryFunctionMatch && primaryFunctionMatch[1] ? primaryFunctionMatch[1] : 'your_function';
162
+
163
+
164
+
165
+ return `
166
+
167
+ # Набор Инструкций для Создания Нового Инструмента
168
+
169
+
170
+
171
+ ## 1. 📂 Структура проекта
172
+
173
+ Создайте следующую структуру папок и файлов (или просто нажмите "Скачать .zip"):
174
+
175
+
176
+
177
+ ${toolName}/
178
+
179
+ ├── .gemini/
180
+
181
+ │ └── settings.json
182
+
183
+ ├── GEMINI.md
184
+
185
+ ├── setup.py
186
+
187
+ └── server.py
188
+
189
+
190
+
191
+ ## 2. 🐍 Содержимое файлов
192
+
193
+ Скопируйте содержимое из соответствующих вкладок в созданные файлы.
194
+
195
+
196
+
197
+ ## 3. 📦 Установка и запуск
198
+
199
+ Откройте терминал в папке \`${toolName}\` и выполните:
200
+
201
+
202
+
203
+ \`\`\`bash
204
+
205
+ # Установка зависимостей и инструмента в режиме разработки
206
+
207
+ pip install -e .
208
+
209
+
210
+
211
+ # Запуск инструмента (Gemini CLI сделает это автоматически)
212
+
213
+ # Для ручной проверки можно запустить:
214
+
215
+ python server.py
216
+
217
+ \`\`\`
218
+
219
+
220
+
221
+ ## 4. 🚀 Использование в Gemini CLI
222
+
223
+ После установки Gemini CLI автоматически обнаружит ваш инструмент.
224
+
225
+ См. вкладку "Примеры", чтобы увидеть, как ваши запросы на естественном языке будут превращаться в вызовы функций.
226
+
227
+
228
+
229
+ ### ⚠️ Правило вызова
230
+
231
+ \`\`\`
232
+
233
+ ${toolName}:${primaryFunction}
234
+
235
+ \`\`\`
236
+
237
+ `;
238
+
239
+ };
240
+
241
+
242
+
243
+ return (
244
+
245
+ <div className="bg-gray-800/50 border border-gray-700 rounded-lg overflow-hidden h-full flex flex-col">
246
+
247
+ <div className="flex-shrink-0 bg-gray-900/70 border-b border-gray-700 flex justify-between items-center pr-2">
248
+
249
+ <nav className="flex space-x-1 p-1" aria-label="Tabs">
250
+
251
+ {tabs.map(tab => (
252
+
253
+ <button
254
+
255
+ key={tab}
256
+
257
+ onClick={() => setActiveTab(tab)}
258
+
259
+ className={`whitespace-nowrap px-3 py-2 text-sm font-medium rounded-md transition ${
260
+
261
+ activeTab === tab
262
+
263
+ ? 'bg-sky-600 text-white'
264
+
265
+ : 'text-gray-400 hover:bg-gray-700 hover:text-gray-200'
266
+
267
+ }`}
268
+
269
+ >
270
+
271
+ {tab}
272
+
273
+ </button>
274
+
275
+ ))}
276
+
277
+ </nav>
278
+
279
+ {generatedCode && (
280
+
281
+ <button
282
+
283
+ onClick={handleDownloadZip}
284
+
285
+ className="bg-sky-600 hover:bg-sky-500 text-white px-3 py-1.5 rounded-md text-xs font-semibold transition disabled:opacity-50"
286
+
287
+ disabled={!generatedCode}
288
+
289
+ >
290
+
291
+ Скачать .zip
292
+
293
+ </button>
294
+
295
+ )}
296
+
297
+ </div>
298
+
299
+ <div className="relative flex-grow min-h-0">
300
+
301
+ <pre className="h-full overflow-auto p-4 text-sm font-mono bg-gray-800">
302
+
303
+ <code className={`language-${getLanguageForTab(activeTab)}`}>
304
+
305
+ {getContentForTab(activeTab)}
306
+
307
+ </code>
308
+
309
+ </pre>
310
+
311
+ {activeTab !== 'Инструкция' && generatedCode && (
312
+
313
+ <button
314
+
315
+ onClick={() => handleCopy(getContentForTab(activeTab), activeTab)}
316
+
317
+ className="absolute top-4 right-4 bg-gray-700 hover:bg-gray-600 text-gray-200 px-3 py-1 rounded-md text-xs transition"
318
+
319
+ >
320
+
321
+ {copiedTab === activeTab ? 'Скопировано!' : 'Копировать'}
322
+
323
+ </button>
324
+
325
+ )}
326
+
327
+ </div>
328
+
329
+ </div>
330
+
331
+ );
332
+
333
+ };
334
+
335
+
336
+
337
+ export default CodeOutput;
338
+
339
+
340
+
FastMCPToolGenerator/components/FunctionEditor.tsx ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { FunctionDefinition, ParameterDefinition, ParameterType } from '../types';
4
+ import { PARAMETER_TYPES, PARAMETER_TYPE_DESCRIPTIONS } from '../constants';
5
+ import Input from './ui/Input';
6
+ import Textarea from './ui/Textarea';
7
+ import Select from './ui/Select';
8
+ import Label from './ui/Label';
9
+ import Button from './ui/Button';
10
+ import Card from './ui/Card';
11
+
12
+ interface FunctionEditorProps {
13
+ func: FunctionDefinition;
14
+ updateFunction: (func: FunctionDefinition) => void;
15
+ removeFunction: () => void;
16
+ isOnlyFunction: boolean;
17
+ }
18
+
19
+ const ParameterRow: React.FC<{
20
+ param: ParameterDefinition;
21
+ updateParameter: (param: ParameterDefinition) => void;
22
+ removeParameter: () => void;
23
+ }> = ({ param, updateParameter, removeParameter }) => {
24
+
25
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
26
+ const { name, value, type } = e.target;
27
+ const isCheckbox = type === 'checkbox';
28
+ const checked = isCheckbox ? (e.target as HTMLInputElement).checked : undefined;
29
+
30
+ updateParameter({
31
+ ...param,
32
+ [name]: isCheckbox ? checked : value
33
+ });
34
+ };
35
+
36
+ return (
37
+ <div className="grid grid-cols-1 md:grid-cols-[1fr,1fr,2fr,auto,auto] gap-2 items-center p-2 bg-gray-900/50 rounded">
38
+ <Input name="name" value={param.name} onChange={handleChange} placeholder="Имя параметра" />
39
+ <div>
40
+ <Select name="type" value={param.type} onChange={handleChange}>
41
+ {PARAMETER_TYPES.map(t => <option key={t} value={t} title={PARAMETER_TYPE_DESCRIPTIONS[t]}>{t}</option>)}
42
+ </Select>
43
+ <p className="text-xs text-gray-400 mt-1 pl-1">{PARAMETER_TYPE_DESCRIPTIONS[param.type]}</p>
44
+ </div>
45
+ <Input name="description" value={param.description} onChange={handleChange} placeholder="Описание" />
46
+ <label className="flex items-center space-x-2 text-sm text-gray-300 justify-center">
47
+ <input type="checkbox" name="required" checked={param.required} onChange={handleChange} className="rounded bg-gray-700 border-gray-600 text-sky-500 focus:ring-sky-500"/>
48
+ <span>Req.</span>
49
+ </label>
50
+ <Button onClick={removeParameter} variant="danger" className="px-2 py-1 text-xs">X</Button>
51
+ </div>
52
+ );
53
+ };
54
+
55
+
56
+ const FunctionEditor: React.FC<FunctionEditorProps> = ({ func, updateFunction, removeFunction, isOnlyFunction }) => {
57
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
58
+ updateFunction({ ...func, [e.target.name]: e.target.value });
59
+ };
60
+
61
+ const addParameter = () => {
62
+ const newParam: ParameterDefinition = {
63
+ id: crypto.randomUUID(),
64
+ name: `param_${func.parameters.length + 1}`,
65
+ type: ParameterType.STRING,
66
+ description: '',
67
+ required: true
68
+ };
69
+ updateFunction({ ...func, parameters: [...func.parameters, newParam] });
70
+ };
71
+
72
+ const updateParameter = (updatedParam: ParameterDefinition) => {
73
+ updateFunction({
74
+ ...func,
75
+ parameters: func.parameters.map(p => p.id === updatedParam.id ? updatedParam : p)
76
+ });
77
+ };
78
+
79
+ const removeParameter = (id: string) => {
80
+ updateFunction({
81
+ ...func,
82
+ parameters: func.parameters.filter(p => p.id !== id)
83
+ });
84
+ };
85
+
86
+ return (
87
+ <Card>
88
+ <div className="flex justify-between items-start mb-4">
89
+ <div className="flex-grow">
90
+ <Label htmlFor={`func-name-${func.id}`}>Имя функции</Label>
91
+ <Input id={`func-name-${func.id}`} name="name" value={func.name} onChange={handleInputChange} />
92
+ </div>
93
+ <Button onClick={removeFunction} variant="danger" disabled={isOnlyFunction} className="ml-4 mt-6">
94
+ Удалить функцию
95
+ </Button>
96
+ </div>
97
+
98
+ <div>
99
+ <Label htmlFor={`func-desc-${func.id}`}>Описание функции</Label>
100
+ <Textarea id={`func-desc-${func.id}`} name="description" value={func.description} onChange={handleInputChange} />
101
+ </div>
102
+
103
+ <div className="mt-4">
104
+ <h4 className="font-semibold text-gray-300 mb-2">Параметры</h4>
105
+ <div className="flex flex-col gap-2">
106
+ {func.parameters.length > 0 && (
107
+ <div className="hidden md:grid md:grid-cols-[1fr,1fr,2fr,auto,auto] gap-2 text-xs text-gray-400 px-2">
108
+ <span>Имя</span>
109
+ <span>РўРёРї</span>
110
+ <span>Описание</span>
111
+ <span className="text-center">РћР±СЏР·.</span>
112
+ </div>
113
+ )}
114
+ {func.parameters.map((param) => (
115
+ <ParameterRow
116
+ key={param.id}
117
+ param={param}
118
+ updateParameter={updateParameter}
119
+ removeParameter={() => removeParameter(param.id)}
120
+ />
121
+ ))}
122
+ </div>
123
+ <Button onClick={addParameter} variant="secondary" className="mt-3 text-xs">+ Добавить параметр</Button>
124
+ </div>
125
+
126
+ <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
127
+ <div>
128
+ <Label htmlFor={`func-return-type-${func.id}`}>Тип возвращаемого значения</Label>
129
+ <Select id={`func-return-type-${func.id}`} name="returnType" value={func.returnType} onChange={handleInputChange}>
130
+ {PARAMETER_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
131
+ </Select>
132
+ </div>
133
+ <div>
134
+ <Label htmlFor={`func-return-desc-${func.id}`}>Описание возвращаемого значения</Label>
135
+ <Input id={`func-return-desc-${func.id}`} name="returnDescription" value={func.returnDescription} onChange={handleInputChange} />
136
+ </div>
137
+ </div>
138
+ </Card>
139
+ );
140
+ };
141
+
142
+ export default FunctionEditor;
143
+
144
+
145
+
146
+
FastMCPToolGenerator/components/ToolForm.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { ToolDefinition, FunctionDefinition, ToolType, ParameterType } from '../types';
4
+ import { TOOL_TYPES } from '../constants';
5
+ import FunctionEditor from './FunctionEditor';
6
+ import Input from './ui/Input';
7
+ import Select from './ui/Select';
8
+ import Label from './ui/Label';
9
+ import Button from './ui/Button';
10
+ import Card from './ui/Card';
11
+
12
+ interface ToolFormProps {
13
+ toolDefinition: ToolDefinition;
14
+ setToolDefinition: React.Dispatch<React.SetStateAction<ToolDefinition>>;
15
+ }
16
+
17
+ const ToolForm: React.FC<ToolFormProps> = ({ toolDefinition, setToolDefinition }) => {
18
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
19
+ const { name, value } = e.target;
20
+ setToolDefinition(prev => ({ ...prev, [name]: value }));
21
+ };
22
+
23
+ const addFunction = () => {
24
+ const newFunction: FunctionDefinition = {
25
+ id: crypto.randomUUID(),
26
+ name: `new_function_${toolDefinition.functions.length + 1}`,
27
+ description: 'Описание новой функции.',
28
+ parameters: [],
29
+ returnType: ParameterType.STRING,
30
+ returnDescription: 'Описание возвращаемого значения.'
31
+ };
32
+ setToolDefinition(prev => ({
33
+ ...prev,
34
+ functions: [...prev.functions, newFunction]
35
+ }));
36
+ };
37
+
38
+ const updateFunction = (updatedFunction: FunctionDefinition) => {
39
+ setToolDefinition(prev => ({
40
+ ...prev,
41
+ functions: prev.functions.map(fn => fn.id === updatedFunction.id ? updatedFunction : fn)
42
+ }));
43
+ };
44
+
45
+ const removeFunction = (id: string) => {
46
+ setToolDefinition(prev => ({
47
+ ...prev,
48
+ functions: prev.functions.filter(fn => fn.id !== id)
49
+ }));
50
+ };
51
+
52
+ return (
53
+ <div className="flex flex-col gap-6">
54
+ <Card>
55
+ <h2 className="text-2xl font-bold mb-4 text-sky-400">1. Конфигурация инструмента</h2>
56
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
57
+ <div>
58
+ <Label htmlFor="name">Название инструмента (Tool Name)</Label>
59
+ <Input
60
+ id="name"
61
+ name="name"
62
+ value={toolDefinition.name}
63
+ onChange={handleInputChange}
64
+ placeholder="e.g., web_scraper"
65
+ />
66
+ </div>
67
+ <div>
68
+ <Label htmlFor="type">Тип инструмента (Tool Type)</Label>
69
+ <Select id="type" name="type" value={toolDefinition.type} onChange={handleInputChange}>
70
+ {TOOL_TYPES.map(type => (
71
+ <option key={type} value={type}>{type}</option>
72
+ ))}
73
+ </Select>
74
+ </div>
75
+ <div className="md:col-span-2">
76
+ <Label htmlFor="dependencies">Зависимости (Dependencies)</Label>
77
+ <Input
78
+ id="dependencies"
79
+ name="dependencies"
80
+ value={toolDefinition.dependencies}
81
+ onChange={handleInputChange}
82
+ placeholder="e.g., requests, beautifulsoup4"
83
+ />
84
+ <p className="text-xs text-gray-500 mt-1">Перечислите через запятую или пробел.</p>
85
+ </div>
86
+ </div>
87
+ </Card>
88
+
89
+ <div className="flex flex-col gap-6">
90
+ <h2 className="text-2xl font-bold text-sky-400">2. Функции</h2>
91
+ {toolDefinition.functions.map((func, index) => (
92
+ <FunctionEditor
93
+ key={func.id}
94
+ func={func}
95
+ updateFunction={updateFunction}
96
+ removeFunction={() => removeFunction(func.id)}
97
+ isOnlyFunction={toolDefinition.functions.length === 1}
98
+ />
99
+ ))}
100
+ </div>
101
+
102
+ <div className="mt-2">
103
+ <Button onClick={addFunction} variant="secondary">
104
+ + Добавить функцию
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ );
109
+ };
110
+
111
+ export default ToolForm;
112
+
113
+
114
+
115
+
FastMCPToolGenerator/components/ui/Button.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5
+ variant?: 'primary' | 'secondary' | 'danger';
6
+ }
7
+
8
+ const Button: React.FC<ButtonProps> = ({ variant = 'primary', className, children, ...props }) => {
9
+ const baseClasses = "px-4 py-2 rounded-md font-semibold text-sm transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed";
10
+
11
+ const variantClasses = {
12
+ primary: 'bg-sky-600 text-white hover:bg-sky-500 focus:ring-sky-500',
13
+ secondary: 'bg-gray-700 text-gray-200 hover:bg-gray-600 focus:ring-gray-500',
14
+ danger: 'bg-red-600 text-white hover:bg-red-500 focus:ring-red-500',
15
+ };
16
+
17
+ return (
18
+ <button className={`${baseClasses} ${variantClasses[variant]} ${className}`} {...props}>
19
+ {children}
20
+ </button>
21
+ );
22
+ };
23
+
24
+ export default Button;
25
+
26
+
27
+
28
+
FastMCPToolGenerator/components/ui/Card.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ interface CardProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ const Card: React.FC<CardProps> = ({ children, className }) => {
10
+ return (
11
+ <div className={`bg-gray-800/50 border border-gray-700 rounded-lg p-6 ${className}`}>
12
+ {children}
13
+ </div>
14
+ );
15
+ };
16
+
17
+ export default Card;
18
+
19
+
20
+
21
+
FastMCPToolGenerator/components/ui/Input.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
5
+
6
+ const Input: React.FC<InputProps> = (props) => {
7
+ return (
8
+ <input
9
+ {...props}
10
+ className="w-full bg-gray-800 border border-gray-600 rounded-md px-3 py-2 text-gray-200 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition"
11
+ />
12
+ );
13
+ };
14
+
15
+ export default Input;
16
+
17
+
18
+
19
+
FastMCPToolGenerator/components/ui/Label.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement>;
5
+
6
+ const Label: React.FC<LabelProps> = (props) => {
7
+ return (
8
+ <label {...props} className="block text-sm font-medium text-gray-300 mb-1" />
9
+ );
10
+ };
11
+
12
+ export default Label;
13
+
14
+
15
+
16
+
FastMCPToolGenerator/components/ui/Select.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement>;
5
+
6
+ const Select: React.FC<SelectProps> = (props) => {
7
+ return (
8
+ <select
9
+ {...props}
10
+ className="w-full bg-gray-800 border border-gray-600 rounded-md px-3 py-2 text-gray-200 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition appearance-none"
11
+ style={{
12
+ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
13
+ backgroundPosition: 'right 0.5rem center',
14
+ backgroundRepeat: 'no-repeat',
15
+ backgroundSize: '1.5em 1.5em',
16
+ paddingRight: '2.5rem',
17
+ }}
18
+ >
19
+ {props.children}
20
+ </select>
21
+ );
22
+ };
23
+
24
+ export default Select;
25
+
26
+
27
+
28
+
FastMCPToolGenerator/components/ui/Textarea.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
5
+
6
+ const Textarea: React.FC<TextareaProps> = (props) => {
7
+ return (
8
+ <textarea
9
+ {...props}
10
+ className="w-full bg-gray-800 border border-gray-600 rounded-md px-3 py-2 text-gray-200 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition"
11
+ rows={2}
12
+ />
13
+ );
14
+ };
15
+
16
+ export default Textarea;
17
+
18
+
19
+
20
+
FastMCPToolGenerator/constants.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ToolType, ParameterType } from './types';
3
+
4
+ export const TOOL_TYPES: ToolType[] = Object.values(ToolType);
5
+ export const PARAMETER_TYPES: ParameterType[] = Object.values(ParameterType);
6
+
7
+ export const PARAMETER_TYPE_DESCRIPTIONS: Record<ParameterType, string> = {
8
+ [ParameterType.STRING]: 'Текстовая строка (e.g., "hello world").',
9
+ [ParameterType.INTEGER]: 'Целое число (e.g., 10, -5).',
10
+ [ParameterType.FLOAT]: 'Число с плавающей точкой (e.g., 3.14).',
11
+ [ParameterType.BOOLEAN]: 'Логическое значение (True или False).',
12
+ [ParameterType.LIST]: 'Упорядоченный список элементов (e.g., ["a", "b"]).',
13
+ [ParameterType.DICTIONARY]: 'Словарь пар ключ-значение (e.g., {"key": "value"}).'
14
+ };
15
+
16
+
17
+
18
+
FastMCPToolGenerator/dist/assets/index-CjZgBbLF.js ADDED
The diff for this file is too large to render. See raw diff
 
FastMCPToolGenerator/dist/index.html ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>FastMCP Tool Generator</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
10
+ <style>
11
+ /* Custom scrollbar for webkit browsers */
12
+ ::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+ ::-webkit-scrollbar-track {
17
+ background: #1f2937; /* bg-gray-800 */
18
+ }
19
+ ::-webkit-scrollbar-thumb {
20
+ background: #4b5563; /* bg-gray-600 */
21
+ border-radius: 4px;
22
+ }
23
+ ::-webkit-scrollbar-thumb:hover {
24
+ background: #6b7280; /* bg-gray-500 */
25
+ }
26
+ </style>
27
+
28
+ <link rel="stylesheet" href="/index.css">
29
+ <script type="importmap">
30
+ {
31
+ "imports": {
32
+ "react": "https://aistudiocdn.com/react@^19.2.0",
33
+ "react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
34
+ "react/": "https://aistudiocdn.com/react@^19.2.0/",
35
+ "@google/genai": "https://aistudiocdn.com/@google/genai@^1.29.0"
36
+ }
37
+ }
38
+ </script>
39
+ <script type="module" crossorigin src="/assets/index-CjZgBbLF.js"></script>
40
+ </head>
41
+ <body class="bg-gray-900 text-gray-100">
42
+ <div id="root"></div>
43
+ </body>
44
+ </html>
45
+
46
+
FastMCPToolGenerator/examples.ts ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ToolDefinition, ToolType, ParameterType } from './types';
3
+
4
+ export const nmapScannerExample: ToolDefinition = {
5
+ name: 'nmap_scanner',
6
+ type: ToolType.UTILITY,
7
+ dependencies: 'python-nmap',
8
+ functions: [
9
+ {
10
+ id: 'nmap-func-1',
11
+ name: 'run_scan',
12
+ description: 'Запускает сканирование Nmap на указанные цели с заданными аргументами. Позволяет выполнять гибкую разведку сети.',
13
+ parameters: [
14
+ {
15
+ id: 'nmap-param-1',
16
+ name: 'targets',
17
+ type: ParameterType.STRING,
18
+ description: "Цели для сканирования (IP, домен, CIDR). Например: '192.168.1.1', 'scanme.nmap.org', '10.0.0.0/24'.",
19
+ required: true,
20
+ },
21
+ {
22
+ id: 'nmap-param-2',
23
+ name: 'ports',
24
+ type: ParameterType.STRING,
25
+ description: "Порты для сканирования. Например: '21-25,80,443,8080'.",
26
+ required: true,
27
+ },
28
+ {
29
+ id: 'nmap-param-3',
30
+ name: 'arguments',
31
+ type: ParameterType.STRING,
32
+ description: "Дополнительные аргументы Nmap для тонкой настройки сканирования. Например: '-sV -sC -O'.",
33
+ required: true,
34
+ },
35
+ ],
36
+ returnType: ParameterType.DICTIONARY,
37
+ returnDescription: 'Результат сканирования в формате JSON, содержащий информацию о хостах, портах, службах и их версиях.',
38
+ },
39
+ {
40
+ id: 'nmap-func-2',
41
+ name: 'host_discovery',
42
+ description: 'Обнаруживает активные хосты в указанной сети (ping scan), не проводя сканирование портов.',
43
+ parameters: [
44
+ {
45
+ id: 'nmap-param-4',
46
+ name: 'network_cidr',
47
+ type: ParameterType.STRING,
48
+ description: "Сетевой диапазон в формате CIDR для обнаружения хостов. Например: '192.168.1.0/24'.",
49
+ required: true,
50
+ },
51
+ ],
52
+ returnType: ParameterType.LIST,
53
+ returnDescription: 'Список IP-адресов активных хостов, обнаруженных в сети.',
54
+ },
55
+ ],
56
+ };
57
+
58
+
59
+
60
+
FastMCPToolGenerator/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>FastMCP Tool Generator</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
10
+ <style>
11
+ /* Custom scrollbar for webkit browsers */
12
+ ::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+ ::-webkit-scrollbar-track {
17
+ background: #1f2937; /* bg-gray-800 */
18
+ }
19
+ ::-webkit-scrollbar-thumb {
20
+ background: #4b5563; /* bg-gray-600 */
21
+ border-radius: 4px;
22
+ }
23
+ ::-webkit-scrollbar-thumb:hover {
24
+ background: #6b7280; /* bg-gray-500 */
25
+ }
26
+ </style>
27
+ <script type="importmap">
28
+ {
29
+ "imports": {
30
+ "react": "https://aistudiocdn.com/react@^19.2.0",
31
+ "react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
32
+ "react/": "https://aistudiocdn.com/react@^19.2.0/",
33
+ "@google/genai": "https://aistudiocdn.com/@google/genai@^1.29.0"
34
+ }
35
+ }
36
+ </script>
37
+ <link rel="stylesheet" href="/index.css">
38
+ </head>
39
+ <body class="bg-gray-900 text-gray-100">
40
+ <div id="root"></div>
41
+ <script type="module" src="/index.tsx"></script>
42
+ </body>
43
+ </html>
44
+
45
+
FastMCPToolGenerator/index.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom/client';
4
+ import App from './App';
5
+
6
+ const rootElement = document.getElementById('root');
7
+ if (!rootElement) {
8
+ throw new Error("Could not find root element to mount to");
9
+ }
10
+
11
+ const root = ReactDOM.createRoot(rootElement);
12
+ root.render(
13
+ <React.StrictMode>
14
+ <App />
15
+ </React.StrictMode>
16
+ );
17
+
18
+
19
+
20
+
FastMCPToolGenerator/metadata.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Copy of Copy of Copy of FastMCP Tool Generator",
3
+ "description": "A web application to generate complete, self-contained, and deployable FastMCP tool boilerplates for Gemini CLI based on user's technical requirements. It creates all necessary files including server logic, setup configuration, and agent instructions.",
4
+ "requestFramePermissions": []
5
+ }
6
+
7
+
FastMCPToolGenerator/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
FastMCPToolGenerator/package.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ {
3
+ "name": "fastmcp-tool-generator-hf",
4
+ "version": "0.0.0",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "autoprefixer": "^10.4.19",
14
+ "axios": "^1.7.2",
15
+ "highlight.js": "^11.9.0",
16
+ "jszip": "^3.10.1",
17
+ "postcss": "^8.4.38",
18
+ "react": "^18.2.0",
19
+ "react-dom": "^18.2.0",
20
+ "react-hot-toast": "^2.4.1",
21
+ "tailwindcss": "^3.4.4"
22
+ },
23
+ "devDependencies": {
24
+ "@types/highlight.js": "^10.1.0",
25
+ "@types/jszip": "^3.10.1",
26
+ "@types/react": "^18.2.66",
27
+ "@types/react-dom": "^18.2.22",
28
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
29
+ "@typescript-eslint/parser": "^7.2.0",
30
+ "@vitejs/plugin-react": "^4.2.1",
31
+ "eslint": "^8.57.0",
32
+ "eslint-plugin-react-hooks": "^4.6.0",
33
+ "eslint-plugin-react-refresh": "^0.4.6",
34
+ "typescript": "^5.2.2",
35
+ "vite": "^5.2.0"
36
+ }
37
+ }
38
+
39
+
40
+
41
+
42
+
FastMCPToolGenerator/requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ fastapi
3
+
4
+ uvicorn[standard]
5
+
6
+ google-generativeai
7
+
8
+ pydantic
9
+
10
+ python-dotenv
11
+
FastMCPToolGenerator/services/aiService.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ToolDefinition, FunctionDefinition } from '../types';
3
+
4
+
5
+
6
+ export async function generateToolDefinitionFromPrompt(prompt: string): Promise<string> {
7
+
8
+ console.log("Отправка запроса на бэкенд /api/generate...");
9
+
10
+
11
+
12
+ try {
13
+
14
+ const response = await fetch('/api/generate', {
15
+
16
+ method: 'POST',
17
+
18
+ headers: {
19
+
20
+ 'Content-Type': 'application/json',
21
+
22
+ },
23
+
24
+ body: JSON.stringify({ prompt: prompt }),
25
+
26
+ });
27
+
28
+
29
+
30
+ if (!response.ok) {
31
+
32
+ const errorData = await response.json();
33
+
34
+ throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
35
+
36
+ }
37
+
38
+
39
+
40
+ const data = await response.json();
41
+
42
+
43
+
44
+ // Бэкенд теперь возвращает объект { jsonString: "..." }
45
+
46
+ if (typeof data.jsonString !== 'string') {
47
+
48
+ throw new Error("Бэкенд вернул неверный формат ответа.");
49
+
50
+ }
51
+
52
+
53
+
54
+ return data.jsonString;
55
+
56
+
57
+
58
+ } catch (error) {
59
+
60
+ console.error("Ошибка при вызове бэкенда /api/generate:", error);
61
+
62
+ throw new Error(`Не удалось связаться с сервером: ${error.message}`);
63
+
64
+ }
65
+
66
+ }
67
+
68
+
69
+
70
+
71
+
FastMCPToolGenerator/services/codeGenerator.ts ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ToolDefinition, FunctionDefinition, ParameterDefinition, ParameterType, GeneratedCode } from '../types';
3
+
4
+ const toPythonType = (type: ParameterType): string => {
5
+ const mapping: Record<ParameterType, string> = {
6
+ [ParameterType.STRING]: 'str',
7
+ [ParameterType.INTEGER]: 'int',
8
+ [ParameterType.FLOAT]: 'float',
9
+ [ParameterType.BOOLEAN]: 'bool',
10
+ [ParameterType.LIST]: 'list',
11
+ [ParameterType.DICTIONARY]: 'dict',
12
+ };
13
+ return mapping[type] || 'Any';
14
+ };
15
+
16
+ const generateSetupPy = (def: ToolDefinition): string => {
17
+ const dependencies = def.dependencies.split(/[\s,]+/).filter(Boolean);
18
+ const installRequires = dependencies.length > 0
19
+ ? `install_requires=${JSON.stringify(dependencies)},`
20
+ : '';
21
+
22
+ return `
23
+ from setuptools import setup, find_packages
24
+
25
+ setup(
26
+ name='${def.name}',
27
+ version='0.1.0',
28
+ packages=find_packages(),
29
+ ${installRequires}
30
+ entry_points={
31
+ 'gemini_tools': [
32
+ '${def.name} = server:main',
33
+ ],
34
+ },
35
+ )
36
+ `.trim();
37
+ };
38
+
39
+ const generateServerPy = (def: ToolDefinition): string => {
40
+ const functionStrings = def.functions.map(func => generateFunction(def, func)).join('\n\n');
41
+ const imports = def.name === 'nmap_scanner' ? 'import nmap' : '# TODO: Добавьте необходимые импорты';
42
+
43
+ return `
44
+ # -*- coding: utf-8 -*-
45
+ import sys
46
+ import json
47
+ from typing import Any, Dict, List
48
+
49
+ # =====================================================================================
50
+ # ⚠️ Важно: Этот код сгенерирован автоматически.
51
+ # Внесите свои изменения в секцию "Реализация функций"
52
+ # =====================================================================================
53
+
54
+ # =====================================================================================
55
+ # Секция: Импорты
56
+ # =====================================================================================
57
+ ${imports}
58
+
59
+
60
+ # =====================================================================================
61
+ # Секция: Реализация функций
62
+ # =====================================================================================
63
+ # TODO: Добавьте здесь вашу бизнес-логику для каждой функции.
64
+
65
+ ${functionStrings}
66
+
67
+
68
+ # =====================================================================================
69
+ # Секция: FastMCP/STDIO Транспорт (Не изменять)
70
+ # =====================================================================================
71
+
72
+ def _get_function_schema(func) -> Dict[str, Any]:
73
+ """Генерирует OpenAPI-совместимую схему из докстринга функции."""
74
+ if not func.__doc__:
75
+ return {}
76
+
77
+ lines = func.__doc__.strip().split('\\n')
78
+ description = lines[0].strip()
79
+
80
+ properties = {}
81
+ required = []
82
+
83
+ args_section = False
84
+ for line in lines:
85
+ line = line.strip()
86
+ if line.startswith('Args:'):
87
+ args_section = True
88
+ continue
89
+ if line.startswith('Returns:'):
90
+ args_section = False
91
+ continue
92
+
93
+ if args_section and ':' in line:
94
+ # Обработка необязательных параметров, обозначенных (*), например "аргумент (*str*):"
95
+ is_required = not line.strip().startswith('(')
96
+
97
+ arg_name_part, arg_desc = line.split(':', 1)
98
+ arg_name = arg_name_part.split('(')[0].strip()
99
+
100
+ # Предполагаем тип из аннотации
101
+ param_type_hint = func.__annotations__.get(arg_name, 'string')
102
+ if hasattr(param_type_hint, '__name__'):
103
+ param_type_hint = param_type_hint.__name__
104
+
105
+ openapi_type = {
106
+ 'str': 'string',
107
+ 'int': 'integer',
108
+ 'float': 'number',
109
+ 'bool': 'boolean',
110
+ 'list': 'array',
111
+ 'dict': 'object'
112
+ }.get(param_type_hint, 'string')
113
+
114
+ properties[arg_name] = {
115
+ 'type': openapi_type,
116
+ 'description': arg_desc.strip()
117
+ }
118
+ if is_required:
119
+ required.append(arg_name)
120
+
121
+ return {
122
+ "name": func.__name__,
123
+ "description": description,
124
+ "parameters": {
125
+ "type": "object",
126
+ "properties": properties,
127
+ "required": required
128
+ }
129
+ }
130
+
131
+
132
+ def main():
133
+ """Главная функция для обработки stdio."""
134
+ functions = {
135
+ ${def.functions.map(f => `"${f.name}": ${f.name}`).join(',\n ')}
136
+ }
137
+
138
+ if len(sys.argv) > 1 and sys.argv[1] == 'discover':
139
+ tool_schema = {
140
+ "name": "${def.name}",
141
+ "tool_spec": {
142
+ "function_declarations": [
143
+ _get_function_schema(func) for func in functions.values()
144
+ ]
145
+ }
146
+ }
147
+ print(json.dumps(tool_schema, ensure_ascii=False))
148
+ return
149
+
150
+ for line in sys.stdin:
151
+ try:
152
+ call = json.loads(line)
153
+ function_name = call['function_call']['name']
154
+ args = call['function_call']['args']
155
+
156
+ if function_name in functions:
157
+ result = functions[function_name](**args)
158
+ response = {
159
+ "tool_response": {
160
+ "name": function_name,
161
+ "content": json.dumps(result, ensure_ascii=False)
162
+ }
163
+ }
164
+ print(json.dumps(response, ensure_ascii=False))
165
+ else:
166
+ # Обработка ошибки: функция не найдена
167
+ error_response = {"tool_response": {"name": function_name, "content": json.dumps({"error": f"Function {function_name} not found."})}}
168
+ print(json.dumps(error_response, ensure_ascii=False))
169
+ except (json.JSONDecodeError, KeyError) as e:
170
+ # Обработка ошибок парсинга или структуры JSON
171
+ error_response = {"tool_response": {"name": "unknown", "content": json.dumps({"error": f"Invalid input JSON: {e}"})}}
172
+ print(json.dumps(error_response, ensure_ascii=False))
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()
177
+ `.trim();
178
+ };
179
+
180
+ const generateFunctionBody = (def: ToolDefinition, func: FunctionDefinition): string => {
181
+ // Generate real, working code for the nmap_scanner example
182
+ if (def.name === 'nmap_scanner') {
183
+ if (func.name === 'run_scan') {
184
+ return `
185
+ try:
186
+ nm = nmap.PortScanner()
187
+ # nmap.scan() возвращает результат сканирования
188
+ scan_result = nm.scan(hosts=targets, ports=ports, arguments=arguments)
189
+ return scan_result
190
+ except nmap.PortScannerError as e:
191
+ return {"error": f"Ошибка сканирования Nmap: {e}. Убедитесь, что nmap установлен и доступен в системном PATH."}
192
+ except Exception as e:
193
+ return {"error": f"Произошла непредвиденная ошибка: {e}"}
194
+ `.trim();
195
+ }
196
+ if (func.name === 'host_discovery') {
197
+ return `
198
+ try:
199
+ nm = nmap.PortScanner()
200
+ # Аргумент -sn выполняет "ping scan" для обнаружения хостов без сканирования портов
201
+ nm.scan(hosts=network_cidr, arguments='-sn')
202
+ # nm.all_hosts() возвращает список хостов, которые находятся в состоянии 'up'
203
+ return {"active_hosts": nm.all_hosts()}
204
+ except nmap.PortScannerError as e:
205
+ return {"error": f"Ошибка сканирования Nmap: {e}. Убедитесь, что nmap установлен и доступен в системном PATH."}
206
+ except Exception as e:
207
+ return {"error": f"Произошла непредвиденная ошибка: {e}"}
208
+ `.trim();
209
+ }
210
+ }
211
+
212
+ // Default placeholder for other tools
213
+ const mockReturnValue = () => {
214
+ switch(func.returnType) {
215
+ case ParameterType.STRING: return '"Результат выполнен��ёСЏ функции"';
216
+ case ParameterType.INTEGER: return '123';
217
+ case ParameterType.FLOAT: return '123.45';
218
+ case ParameterType.BOOLEAN: return 'True';
219
+ case ParameterType.LIST: return '["элемент1", "элемент2"]';
220
+ case ParameterType.DICTIONARY: return '{"ключ": "значение"}';
221
+ default: return 'None';
222
+ }
223
+ };
224
+
225
+ return `
226
+ # TODO: Реализуйте логику функции здесь
227
+ # Это пример. Замените его своей реализацией.
228
+ print(f"Функция '${func.name}' вызвана со следующими аргументами:")
229
+ ${func.parameters.map(p => ` print(f"- ${p.name}: {${p.name}}")`).join('\n')}
230
+
231
+ return ${mockReturnValue()}
232
+ `.trim();
233
+ };
234
+
235
+
236
+ const generateFunction = (def: ToolDefinition, func: FunctionDefinition): string => {
237
+ const paramsString = func.parameters.map(p => {
238
+ const typeHint = toPythonType(p.type);
239
+ const defaultValue = p.required ? '' : ' = None';
240
+ return `${p.name}: ${typeHint}${defaultValue}`;
241
+ }).join(', ');
242
+ const returnTypeString = toPythonType(func.returnType);
243
+
244
+ const docstringParams = func.parameters.map(p =>
245
+ ` ${p.name} (${toPythonType(p.type)}): ${p.description}`
246
+ ).join('\n');
247
+
248
+ const docstring = `
249
+ """${func.description}
250
+
251
+ Args:
252
+ ${docstringParams || ' Нет.'}
253
+
254
+ Returns:
255
+ ${returnTypeString}: ${func.returnDescription}
256
+ """
257
+ `.trim();
258
+
259
+ const body = generateFunctionBody(def, func);
260
+
261
+ return `
262
+ def ${func.name}(${paramsString}) -> ${returnTypeString}:
263
+ ${docstring}
264
+ ${body}
265
+ `.trim();
266
+ };
267
+
268
+
269
+ const generateSettingsJson = (def: ToolDefinition): string => {
270
+ return JSON.stringify({
271
+ servers: [
272
+ {
273
+ name: def.name,
274
+ command: ['python', 'server.py'],
275
+ transport: 'stdio',
276
+ type: def.type
277
+ }
278
+ ]
279
+ }, null, 2);
280
+ };
281
+
282
+ const generateGeminiMd = (def: ToolDefinition): string => {
283
+ const functionDocs = def.functions.map(func => {
284
+ const paramsList = func.parameters.map(p => `- \`${p.name}\` (*${toPythonType(p.type)}*): ${p.description}${p.required ? ' (обязательный)' : ''}`).join('\n');
285
+ return `
286
+ ### \`${def.name}:${func.name}\`
287
+
288
+ ${func.description}
289
+
290
+ **Параметры:**
291
+ ${paramsList || 'Нет параметров.'}
292
+
293
+ **Возвращает:**
294
+ *${toPythonType(func.returnType)}* - ${func.returnDescription}
295
+ `.trim();
296
+ }).join('\n\n---\n\n');
297
+
298
+ const nmapExamples = `
299
+ ## Правила и Примеры Использования
300
+
301
+ **ВАЖНО:** Всегда используй формат \`nmap_scanner:имя_функции()\`.
302
+
303
+ Если пользователь просит **обнаружить активные устройства в сети**, используй \`host_discovery\`.
304
+ *Запрос пользователя:* "Найди все работающие компьютеры в сети 192.168.0.0/24"
305
+ *Твой вызов:* \`nmap_scanner:host_discovery(network_cidr="192.168.0.0/24")\`
306
+
307
+ Если пользователь просит **просканировать порты или найти уязвимости**, используй \`run_scan\`.
308
+ *Запрос пользователя:* "Просканируй хост 192.168.1.1 на наличие открытых портов и определи запущенные службы"
309
+ *Твой вызов:* \`nmap_scanner:run_scan(targets="192.168.1.1", ports="1-1024", arguments="-sV")\`
310
+
311
+ *Запрос пользователя:* "Проведи агрессивное сканирование scanme.nmap.org, чтобы найти возможные векторы ата��и"
312
+ *Твой вызов:* \`nmap_scanner:run_scan(targets="scanme.nmap.org", ports="1-65535", arguments="-A -v")\`
313
+ `.trim();
314
+
315
+ const genericExample = `
316
+ ## Пример использования
317
+
318
+ **Простой вызов:**
319
+ \`\`\`
320
+ ${def.name}:${def.functions[0]?.name}(${def.functions[0]?.parameters.filter(p => p.required).map(p => `${p.name}="значение"`).join(', ')})
321
+ \`\`\`
322
+ `.trim();
323
+
324
+ return `
325
+ # Инструкция для Агента: Инструмент "${def.name}"
326
+
327
+ ## Описание
328
+
329
+ Этот инструмент предоставляет набор утилит для [опишите общую цель инструмента, например: сетевой разведки с помощью Nmap]. Он относится к типу **${def.type}**.
330
+
331
+ ## Функции
332
+
333
+ ${functionDocs}
334
+
335
+ ${def.name === 'nmap_scanner' ? nmapExamples : genericExample}
336
+ `.trim();
337
+ };
338
+
339
+ const getExampleValue = (param: ParameterDefinition): string => {
340
+ const { name, type } = param;
341
+ const lowerParamName = name.toLowerCase();
342
+
343
+ if (type === ParameterType.STRING) {
344
+ if (lowerParamName.includes('target') || lowerParamName.includes('host') || lowerParamName.includes('domain')) return "'scanme.nmap.org'";
345
+ if (lowerParamName.includes('port')) return "'80,443,8080'";
346
+ if (lowerParamName.includes('cidr') || lowerParamName.includes('network')) return "'192.168.1.0/24'";
347
+ if (lowerParamName.includes('argument')) return "'-A -v'";
348
+ if (lowerParamName.includes('file') || lowerParamName.includes('path')) return "'/path/to/file.txt'";
349
+ if (lowerParamName.includes('url')) return "'https://example.com'";
350
+ if (lowerParamName.includes('name')) return "'John Doe'";
351
+ return "'example_value'";
352
+ }
353
+
354
+ switch (type) {
355
+ case ParameterType.INTEGER: return '1024';
356
+ case ParameterType.FLOAT: return '3.14';
357
+ case ParameterType.BOOLEAN: return 'True';
358
+ case ParameterType.LIST: return '["item1", "item2"]';
359
+ case ParameterType.DICTIONARY: return '{"key": "value"}';
360
+ default: return 'None';
361
+ }
362
+ };
363
+
364
+ const getNaturalLanguageExample = (defName: string, func: FunctionDefinition): string => {
365
+ if (defName === 'nmap_scanner') {
366
+ if (func.name === 'run_scan') {
367
+ return '> "Просканируй scanme.nmap.org, чтобы найти возможные векторы атаки."';
368
+ }
369
+ if (func.name === 'host_discovery') {
370
+ return '> "Найди все активные устройства в моей локальной сети 192.168.1.0/24."';
371
+ }
372
+ }
373
+ return `> "Выполни функцию ${func.name} с тестовыми параметрами."`;
374
+ }
375
+
376
+
377
+ const generateExamplesMd = (def: ToolDefinition): string => {
378
+ if (!def.functions || def.functions.length === 0) {
379
+ return '# Примеры использования\n\nИнструмент не содержит функций для генерации примеров.';
380
+ }
381
+
382
+ const exampleSnippets = def.functions.map(func => {
383
+ const paramsString = func.parameters
384
+ .map(p => `${p.name}=${getExampleValue(p)}`)
385
+ .join(', ');
386
+
387
+ const exampleCall = `${def.name}:${func.name}(${paramsString})`;
388
+ const naturalLanguageQuery = getNaturalLanguageExample(def.name, func);
389
+
390
+ return `
391
+ ### Функция: \`${func.name}\`
392
+
393
+ ${func.description}
394
+
395
+ **Пример запроса на естественном языке:**
396
+ ${naturalLanguageQuery}
397
+
398
+ **В ответ Gemini CLI сгенерирует следующий вызов:**
399
+ \`\`\`bash
400
+ ${exampleCall}
401
+ \`\`\`
402
+ `.trim();
403
+ }).join('\n\n---\n\n');
404
+
405
+ return `
406
+ # Примеры использования для инструмента "${def.name}"
407
+
408
+ Gemini CLI понимает естественный язык. Он анализирует ваш запрос и вызывает соответствующую функцию инструмента. Ниже показано, как ваши запросы преобразуются в конкретные вызовы.
409
+
410
+ ---
411
+
412
+ ${exampleSnippets}
413
+ `.trim();
414
+ };
415
+
416
+
417
+ export const generateAllCode = (def: ToolDefinition): GeneratedCode => {
418
+ return {
419
+ setupPy: generateSetupPy(def),
420
+ serverPy: generateServerPy(def),
421
+ settingsJson: generateSettingsJson(def),
422
+ geminiMd: generateGeminiMd(def),
423
+ examplesMd: generateExamplesMd(def),
424
+ };
425
+ };
426
+
427
+
428
+
FastMCPToolGenerator/style.css ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ padding: 2rem;
3
+ font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
+ }
5
+
6
+ h1 {
7
+ font-size: 16px;
8
+ margin-top: 0;
9
+ }
10
+
11
+ p {
12
+ color: rgb(107, 114, 128);
13
+ font-size: 15px;
14
+ margin-bottom: 10px;
15
+ margin-top: 5px;
16
+ }
17
+
18
+ .card {
19
+ max-width: 620px;
20
+ margin: 0 auto;
21
+ padding: 16px;
22
+ border: 1px solid lightgray;
23
+ border-radius: 16px;
24
+ }
25
+
26
+ .card p:last-child {
27
+ margin-bottom: 0;
28
+ }
FastMCPToolGenerator/tsconfig.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "types": [
14
+ "node"
15
+ ],
16
+ "moduleResolution": "bundler",
17
+ "isolatedModules": true,
18
+ "moduleDetection": "force",
19
+ "allowJs": true,
20
+ "jsx": "react-jsx",
21
+ "paths": {
22
+ "@/*": [
23
+ "./*"
24
+ ]
25
+ },
26
+ "allowImportingTsExtensions": true,
27
+ "noEmit": true
28
+ }
29
+ }
30
+
31
+
32
+
FastMCPToolGenerator/types.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ export enum ToolType {
3
+ REST_API = 'REST_API',
4
+ CLI = 'CLI',
5
+ DATABASE = 'DATABASE',
6
+ CUSTOM = 'CUSTOM',
7
+ UTILITY = 'UTILITY',
8
+ CALCULATOR = 'CALCULATOR',
9
+ CONVERTER = 'CONVERTER'
10
+ }
11
+
12
+ export enum ParameterType {
13
+ STRING = 'str',
14
+ INTEGER = 'int',
15
+ FLOAT = 'float',
16
+ BOOLEAN = 'bool',
17
+ LIST = 'list',
18
+ DICTIONARY = 'dict'
19
+ }
20
+
21
+ export interface ParameterDefinition {
22
+ id: string;
23
+ name: string;
24
+ type: ParameterType;
25
+ description: string;
26
+ required: boolean;
27
+ }
28
+
29
+ export interface FunctionDefinition {
30
+ id: string;
31
+ name: string;
32
+ description: string;
33
+ parameters: ParameterDefinition[];
34
+ returnType: ParameterType;
35
+ returnDescription: string;
36
+ }
37
+
38
+ export interface ToolDefinition {
39
+ name: string;
40
+ type: ToolType;
41
+ dependencies: string;
42
+ functions: FunctionDefinition[];
43
+ }
44
+
45
+ export interface GeneratedCode {
46
+ setupPy: string;
47
+ serverPy: string;
48
+ settingsJson: string;
49
+ geminiMd: string;
50
+ examplesMd: string;
51
+ }
52
+
53
+ // Экспортируем для использования в AI промпте
54
+ export const initialFunctionForPrompt: Omit<FunctionDefinition, 'id'> = {
55
+ name: 'example_function',
56
+ description: 'Краткое описание того, что делает эта функция.',
57
+ parameters: [
58
+ {
59
+ id: 'param-id-1', // id здесь для примера, в реальности он генерируется
60
+ name: 'example_param',
61
+ type: ParameterType.STRING,
62
+ description: 'Описание параметра.',
63
+ required: true,
64
+ },
65
+ ],
66
+ returnType: ParameterType.STRING,
67
+ returnDescription: 'Описание возвращаемого значения.',
68
+ };
69
+
70
+
71
+
72
+
FastMCPToolGenerator/vite.config.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+
3
+ import { defineConfig, loadEnv } from 'vite';
4
+
5
+ import react from '@vitejs/plugin-react';
6
+
7
+
8
+
9
+ export default defineConfig(({ mode }) => {
10
+
11
+ const env = loadEnv(mode, '.', '');
12
+
13
+
14
+
15
+ // Порт для FastAPI (бэкенда) при локальной разработке
16
+
17
+ const PYTHON_BACKEND_PORT = 8000;
18
+
19
+
20
+
21
+ return {
22
+
23
+ server: {
24
+
25
+ // Используем порт 3000 для Vite (фронтенда)
26
+
27
+ port: 3000,
28
+
29
+ host: '0.0.0.0',
30
+
31
+ proxy: {
32
+
33
+ // Перенаправляем все /api запросы на наш Python бэкенд
34
+
35
+ '/api': {
36
+
37
+ target: `http://127.0.0.1:${PYTHON_BACKEND_PORT}`, // ИСПРАВЛЕНО
38
+
39
+ changeOrigin: true,
40
+
41
+ secure: false,
42
+
43
+ }
44
+
45
+ }
46
+
47
+ },
48
+
49
+ plugins: [react()],
50
+
51
+ define: {
52
+
53
+ // Ключ API БОЛЬШЕ НЕ НУЖЕН в define!
54
+
55
+ },
56
+
57
+ resolve: {
58
+
59
+ alias: {
60
+
61
+ '@': path.resolve(__dirname, '.'),
62
+
63
+ }
64
+
65
+ }
66
+
67
+ };
68
+
69
+ });
70
+
71
+
72
+
README.md CHANGED
@@ -1,10 +1,20 @@
1
- ---
2
- title: FastMCPToolGenerator
3
- emoji: 🐢
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
3
+ </div>
4
+
5
+ # Run and deploy your AI Studio app
6
+
7
+ This contains everything you need to run your app locally.
8
+
9
+ View your app in AI Studio: https://ai.studio/apps/drive/1DWLDNsbZ8bO_lQPzuDVQB3cpW92HgYCC
10
+
11
+ ## Run Locally
12
+
13
+ **Prerequisites:** Node.js
14
+
15
+
16
+ 1. Install dependencies:
17
+ `npm install`
18
+ 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
19
+ 3. Run the app:
20
+ `npm run dev`