Reubencf commited on
Commit
33d698c
Β·
1 Parent(s): 734142c

Add Flutter tools to Node.js MCP server and remove Python MCP server

Browse files
app/api/flutter/create/route.ts ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data')
6
+ ? '/data'
7
+ : path.join(process.cwd(), 'data')
8
+ const FLUTTER_APPS_DIR = path.join(DATA_DIR, 'documents', 'flutter_apps')
9
+
10
+ // Ensure directory exists
11
+ if (!fs.existsSync(FLUTTER_APPS_DIR)) {
12
+ fs.mkdirSync(FLUTTER_APPS_DIR, { recursive: true })
13
+ }
14
+
15
+ export async function POST(request: NextRequest) {
16
+ try {
17
+ const body = await request.json()
18
+ const { name, dartCode, dependencies = [], pubspecYaml } = body
19
+
20
+ if (!name || !dartCode) {
21
+ return NextResponse.json(
22
+ { success: false, error: 'Name and dartCode are required' },
23
+ { status: 400 }
24
+ )
25
+ }
26
+
27
+ // Sanitize name
28
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()
29
+ if (!safeName) {
30
+ return NextResponse.json(
31
+ { success: false, error: 'Invalid app name' },
32
+ { status: 400 }
33
+ )
34
+ }
35
+
36
+ // Generate default pubspec.yaml if not provided
37
+ let finalPubspecYaml = pubspecYaml
38
+ if (!finalPubspecYaml) {
39
+ const depLines = dependencies.map((dep: string) => ` ${dep}`).join('\n')
40
+ finalPubspecYaml = `name: ${safeName}
41
+ description: A Flutter application created via Reuben OS
42
+ version: 1.0.0
43
+
44
+ environment:
45
+ sdk: '>=3.0.0 <4.0.0'
46
+
47
+ dependencies:
48
+ flutter:
49
+ sdk: flutter
50
+ ${depLines}
51
+
52
+ flutter:
53
+ uses-material-design: true
54
+ `
55
+ }
56
+
57
+ // Create Flutter app object
58
+ const flutterApp = {
59
+ type: 'flutter_app',
60
+ name: safeName,
61
+ dartCode,
62
+ dependencies,
63
+ pubspecYaml: finalPubspecYaml,
64
+ metadata: {
65
+ created: new Date().toISOString(),
66
+ modified: new Date().toISOString(),
67
+ author: 'claude'
68
+ }
69
+ }
70
+
71
+ // Save to file
72
+ const fileName = `${safeName}.flutter.json`
73
+ const filePath = path.join(FLUTTER_APPS_DIR, fileName)
74
+
75
+ // Check if file exists
76
+ if (fs.existsSync(filePath)) {
77
+ return NextResponse.json(
78
+ { success: false, error: `Flutter app '${safeName}' already exists. Use update instead.` },
79
+ { status: 400 }
80
+ )
81
+ }
82
+
83
+ fs.writeFileSync(filePath, JSON.stringify(flutterApp, null, 2), 'utf-8')
84
+
85
+ return NextResponse.json({
86
+ success: true,
87
+ appName: safeName,
88
+ fileName,
89
+ filePath: `flutter_apps/${fileName}`,
90
+ dependencies,
91
+ message: 'Flutter app created successfully'
92
+ })
93
+ } catch (error) {
94
+ console.error('Error creating Flutter app:', error)
95
+ return NextResponse.json(
96
+ { success: false, error: 'Failed to create Flutter app' },
97
+ { status: 500 }
98
+ )
99
+ }
100
+ }
101
+
app/api/flutter/delete/route.ts ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data')
6
+ ? '/data'
7
+ : path.join(process.cwd(), 'data')
8
+ const FLUTTER_APPS_DIR = path.join(DATA_DIR, 'documents', 'flutter_apps')
9
+
10
+ export async function DELETE(request: NextRequest) {
11
+ try {
12
+ const searchParams = request.nextUrl.searchParams
13
+ const name = searchParams.get('name')
14
+
15
+ if (!name) {
16
+ return NextResponse.json(
17
+ { success: false, error: 'App name is required' },
18
+ { status: 400 }
19
+ )
20
+ }
21
+
22
+ // Sanitize name
23
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()
24
+ const fileName = `${safeName}.flutter.json`
25
+ const filePath = path.join(FLUTTER_APPS_DIR, fileName)
26
+
27
+ if (!fs.existsSync(filePath)) {
28
+ return NextResponse.json(
29
+ { success: false, error: `Flutter app '${safeName}' not found` },
30
+ { status: 404 }
31
+ )
32
+ }
33
+
34
+ // Move to trash instead of permanent delete
35
+ const trashDir = path.join(DATA_DIR, '.trash')
36
+ if (!fs.existsSync(trashDir)) {
37
+ fs.mkdirSync(trashDir, { recursive: true })
38
+ }
39
+
40
+ const timestamp = Date.now()
41
+ const trashPath = path.join(trashDir, `${timestamp}_${fileName}`)
42
+ fs.renameSync(filePath, trashPath)
43
+
44
+ return NextResponse.json({
45
+ success: true,
46
+ appName: safeName,
47
+ message: 'Flutter app deleted successfully'
48
+ })
49
+ } catch (error) {
50
+ console.error('Error deleting Flutter app:', error)
51
+ return NextResponse.json(
52
+ { success: false, error: 'Failed to delete Flutter app' },
53
+ { status: 500 }
54
+ )
55
+ }
56
+ }
57
+
app/api/flutter/get/route.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data')
6
+ ? '/data'
7
+ : path.join(process.cwd(), 'data')
8
+ const FLUTTER_APPS_DIR = path.join(DATA_DIR, 'documents', 'flutter_apps')
9
+
10
+ export async function GET(request: NextRequest) {
11
+ try {
12
+ const searchParams = request.nextUrl.searchParams
13
+ const name = searchParams.get('name')
14
+
15
+ if (!name) {
16
+ return NextResponse.json(
17
+ { success: false, error: 'App name is required' },
18
+ { status: 400 }
19
+ )
20
+ }
21
+
22
+ // Sanitize name
23
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()
24
+ const fileName = `${safeName}.flutter.json`
25
+ const filePath = path.join(FLUTTER_APPS_DIR, fileName)
26
+
27
+ if (!fs.existsSync(filePath)) {
28
+ return NextResponse.json(
29
+ { success: false, error: `Flutter app '${safeName}' not found` },
30
+ { status: 404 }
31
+ )
32
+ }
33
+
34
+ const fileContent = fs.readFileSync(filePath, 'utf-8')
35
+ const appData = JSON.parse(fileContent)
36
+
37
+ return NextResponse.json({
38
+ success: true,
39
+ appName: safeName,
40
+ appData
41
+ })
42
+ } catch (error) {
43
+ console.error('Error getting Flutter app:', error)
44
+ return NextResponse.json(
45
+ { success: false, error: 'Failed to get Flutter app' },
46
+ { status: 500 }
47
+ )
48
+ }
49
+ }
50
+
app/api/flutter/list/route.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data')
6
+ ? '/data'
7
+ : path.join(process.cwd(), 'data')
8
+ const FLUTTER_APPS_DIR = path.join(DATA_DIR, 'documents', 'flutter_apps')
9
+
10
+ export async function GET() {
11
+ try {
12
+ if (!fs.existsSync(FLUTTER_APPS_DIR)) {
13
+ return NextResponse.json({
14
+ success: true,
15
+ apps: [],
16
+ count: 0
17
+ })
18
+ }
19
+
20
+ const files = fs.readdirSync(FLUTTER_APPS_DIR)
21
+ const apps = []
22
+
23
+ for (const file of files) {
24
+ if (file.endsWith('.flutter.json')) {
25
+ try {
26
+ const filePath = path.join(FLUTTER_APPS_DIR, file)
27
+ const fileContent = fs.readFileSync(filePath, 'utf-8')
28
+ const appData = JSON.parse(fileContent)
29
+
30
+ apps.push({
31
+ name: appData.name || file.replace('.flutter.json', ''),
32
+ created: appData.metadata?.created,
33
+ modified: appData.metadata?.modified,
34
+ dependenciesCount: appData.dependencies?.length || 0,
35
+ filePath: `flutter_apps/${file}`
36
+ })
37
+ } catch (error) {
38
+ // Skip malformed files
39
+ console.error(`Error parsing ${file}:`, error)
40
+ continue
41
+ }
42
+ }
43
+ }
44
+
45
+ return NextResponse.json({
46
+ success: true,
47
+ apps,
48
+ count: apps.length
49
+ })
50
+ } catch (error) {
51
+ console.error('Error listing Flutter apps:', error)
52
+ return NextResponse.json(
53
+ { success: false, error: 'Failed to list Flutter apps' },
54
+ { status: 500 }
55
+ )
56
+ }
57
+ }
58
+
app/api/flutter/update/route.ts ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data')
6
+ ? '/data'
7
+ : path.join(process.cwd(), 'data')
8
+ const FLUTTER_APPS_DIR = path.join(DATA_DIR, 'documents', 'flutter_apps')
9
+
10
+ export async function PUT(request: NextRequest) {
11
+ try {
12
+ const body = await request.json()
13
+ const { name, dartCode, dependencies, pubspecYaml } = body
14
+
15
+ if (!name) {
16
+ return NextResponse.json(
17
+ { success: false, error: 'App name is required' },
18
+ { status: 400 }
19
+ )
20
+ }
21
+
22
+ // Sanitize name
23
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()
24
+ const fileName = `${safeName}.flutter.json`
25
+ const filePath = path.join(FLUTTER_APPS_DIR, fileName)
26
+
27
+ if (!fs.existsSync(filePath)) {
28
+ return NextResponse.json(
29
+ { success: false, error: `Flutter app '${safeName}' not found` },
30
+ { status: 404 }
31
+ )
32
+ }
33
+
34
+ // Read existing app
35
+ const fileContent = fs.readFileSync(filePath, 'utf-8')
36
+ const appData = JSON.parse(fileContent)
37
+
38
+ // Update fields if provided
39
+ if (dartCode !== undefined) {
40
+ appData.dartCode = dartCode
41
+ }
42
+ if (dependencies !== undefined) {
43
+ appData.dependencies = dependencies
44
+ }
45
+ if (pubspecYaml !== undefined) {
46
+ appData.pubspecYaml = pubspecYaml
47
+ }
48
+
49
+ // Update metadata
50
+ appData.metadata.modified = new Date().toISOString()
51
+
52
+ // Save updated app
53
+ fs.writeFileSync(filePath, JSON.stringify(appData, null, 2), 'utf-8')
54
+
55
+ return NextResponse.json({
56
+ success: true,
57
+ appName: safeName,
58
+ message: 'Flutter app updated successfully'
59
+ })
60
+ } catch (error) {
61
+ console.error('Error updating Flutter app:', error)
62
+ return NextResponse.json(
63
+ { success: false, error: 'Failed to update Flutter app' },
64
+ { status: 500 }
65
+ )
66
+ }
67
+ }
68
+
backend/mcp_server.py DELETED
@@ -1,150 +0,0 @@
1
- """
2
- Reuben OS MCP Server
3
- Provides AI-powered tools for document management, code execution, and desktop environment integration
4
- """
5
-
6
- from mcp.server.fastmcp import FastMCP
7
- import os
8
- from dotenv import load_dotenv
9
- import logging
10
- from pathlib import Path
11
-
12
- # Import tool modules
13
- from tools.document_tools import register_document_tools
14
- from tools.calendar_tools import register_calendar_tools
15
- from tools.ai_study_tools import register_ai_study_tools
16
- from tools.claude_integration_tools import register_claude_integration_tools
17
- from tools.flutter_tools import register_flutter_tools
18
-
19
- # Setup logging
20
- logging.basicConfig(level=logging.INFO)
21
- logger = logging.getLogger(__name__)
22
-
23
- # Load environment variables
24
- load_dotenv()
25
-
26
- # Initialize MCP server
27
- mcp = FastMCP("Reuben OS")
28
-
29
- # Configuration from environment
30
- DATA_DIR = os.getenv("DATA_DIR", "./data")
31
- DOCS_DIR = os.path.join(DATA_DIR, "documents")
32
- DB_PATH = os.path.join(DATA_DIR, "exams", "exams.db")
33
- UPLOAD_PASSCODE = os.getenv("UPLOAD_PASSCODE", "default-passcode-change-me")
34
- MCP_PORT = int(os.getenv("MCP_PORT", "8000"))
35
-
36
- # Ensure directories exist
37
- os.makedirs(DOCS_DIR, exist_ok=True)
38
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
39
-
40
- # Add some sample documents if the directory is empty
41
- def add_sample_documents():
42
- """Add sample documents for testing"""
43
- sample_files = [
44
- ("README.txt", "Welcome to Reuben OS!\n\nThis is your advanced desktop environment with AI-powered tools and code execution capabilities."),
45
- ("study_tips.md", "# Study Tips\n\n1. Create a study schedule\n2. Review notes regularly\n3. Practice past exams\n4. Form study groups\n5. Get enough sleep"),
46
- ("exam_schedule.txt", "Exam Schedule 2025\n\nFebruary:\n- Math Midterm: Feb 15\n- Physics Final: Feb 20\n- Chemistry Midterm: Feb 18"),
47
- ]
48
-
49
- for filename, content in sample_files:
50
- file_path = os.path.join(DOCS_DIR, filename)
51
- if not os.path.exists(file_path):
52
- with open(file_path, 'w', encoding='utf-8') as f:
53
- f.write(content)
54
- logger.info(f"Created sample file: {filename}")
55
-
56
- # Initialize sample documents
57
- add_sample_documents()
58
-
59
- # Register all tools
60
- logger.info(f"Registering document tools with directory: {DOCS_DIR}")
61
- register_document_tools(mcp, DOCS_DIR, UPLOAD_PASSCODE)
62
-
63
- logger.info(f"Registering calendar tools with database: {DB_PATH}")
64
- register_calendar_tools(mcp, DB_PATH)
65
-
66
- logger.info("Registering AI study tools")
67
- register_ai_study_tools(mcp)
68
-
69
- logger.info("Registering Claude integration tools")
70
- register_claude_integration_tools(mcp, DATA_DIR)
71
-
72
- logger.info("Registering Flutter app tools")
73
- register_flutter_tools(mcp, DATA_DIR, UPLOAD_PASSCODE)
74
-
75
- # Add server information tool
76
- @mcp.tool()
77
- def get_server_info() -> dict:
78
- """Get information about the MCP server and available tools"""
79
- return {
80
- "name": "Reuben OS MCP Server",
81
- "version": "1.0.0",
82
- "status": "running",
83
- "configuration": {
84
- "documents_directory": DOCS_DIR,
85
- "database_path": DB_PATH,
86
- "port": MCP_PORT
87
- },
88
- "available_tools": {
89
- "document_tools": [
90
- "list_all_documents",
91
- "get_folder_structure",
92
- "search_documents",
93
- "extract_pdf_text",
94
- "extract_docx_text",
95
- "read_text_file",
96
- "search_in_document",
97
- "get_document_summary",
98
- "upload_file (requires passcode)",
99
- "create_folder (requires passcode)",
100
- "delete_file (requires passcode)"
101
- ],
102
- "calendar_tools": [
103
- "get_all_exams",
104
- "get_exams_this_week",
105
- "get_exams_next_week",
106
- "get_exams_by_month",
107
- "find_exam_by_subject",
108
- "get_exams_by_date",
109
- "get_upcoming_exams",
110
- "search_related_documents",
111
- "get_study_schedule",
112
- "get_exam_statistics"
113
- ],
114
- "claude_integration_tools": [
115
- "save_claude_text",
116
- "save_claude_code",
117
- "execute_python_code",
118
- "execute_python_with_packages",
119
- "execute_matplotlib_code",
120
- "list_claude_generated_content",
121
- "get_code_output"
122
- ],
123
- "flutter_tools": [
124
- "create_flutter_app (requires passcode)",
125
- "get_flutter_app",
126
- "list_flutter_apps",
127
- "update_flutter_app (requires passcode)",
128
- "delete_flutter_app (requires passcode)"
129
- ]
130
- },
131
- "tips": [
132
- "Use 'save_claude_code' to save generated code to Reuben OS",
133
- "Use 'execute_python_code' to run Python scripts directly",
134
- "Use 'execute_matplotlib_code' for data visualization with matplotlib",
135
- "Use 'list_claude_generated_content' to see all saved content",
136
- "Upload operations require a passcode for security",
137
- "Reuben OS can execute your Python code and save outputs automatically!",
138
- "Use 'create_flutter_app' to create Flutter apps that run in Zapp",
139
- "Use 'list_flutter_apps' to see all available Flutter applications"
140
- ]
141
- }
142
-
143
- if __name__ == "__main__":
144
- # Run the MCP server
145
- logger.info(f"Starting Reuben OS MCP Server on port {MCP_PORT}")
146
- logger.info(f"Documents directory: {DOCS_DIR}")
147
- logger.info(f"Database path: {DB_PATH}")
148
-
149
- # Run with SSE transport for Claude Desktop compatibility
150
- mcp.run(transport="sse")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/mcp_server_https.py DELETED
@@ -1,130 +0,0 @@
1
- """
2
- Exam Document Hub MCP Server with HTTPS Support
3
- Provides AI-powered tools for document management and exam calendar
4
- """
5
-
6
- from mcp.server.fastmcp import FastMCP
7
- import os
8
- from dotenv import load_dotenv
9
- import logging
10
- from pathlib import Path
11
- import ssl
12
- import uvicorn
13
-
14
- # Import tool modules
15
- from tools.document_tools import register_document_tools
16
- from tools.calendar_tools import register_calendar_tools
17
-
18
- # Setup logging
19
- logging.basicConfig(level=logging.INFO)
20
- logger = logging.getLogger(__name__)
21
-
22
- # Load environment variables
23
- load_dotenv()
24
-
25
- # Initialize MCP server
26
- mcp = FastMCP("Exam Document Hub")
27
-
28
- # Configuration from environment
29
- DATA_DIR = os.getenv("DATA_DIR", "./data")
30
- DOCS_DIR = os.path.join(DATA_DIR, "documents")
31
- DB_PATH = os.path.join(DATA_DIR, "exams", "exams.db")
32
- UPLOAD_PASSCODE = os.getenv("UPLOAD_PASSCODE", "default-passcode-change-me")
33
- MCP_PORT = int(os.getenv("MCP_PORT", "8443")) # Changed to HTTPS port
34
-
35
- # Ensure directories exist
36
- os.makedirs(DOCS_DIR, exist_ok=True)
37
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
38
-
39
- # Add some sample documents if the directory is empty
40
- def add_sample_documents():
41
- """Add sample documents for testing"""
42
- sample_files = [
43
- ("README.txt", "Welcome to the Exam Document Hub!\n\nThis is your document repository for exam materials."),
44
- ("study_tips.md", "# Study Tips\n\n1. Create a study schedule\n2. Review notes regularly\n3. Practice past exams\n4. Form study groups\n5. Get enough sleep"),
45
- ("exam_schedule.txt", "Exam Schedule 2025\n\nFebruary:\n- Math Midterm: Feb 15\n- Physics Final: Feb 20\n- Chemistry Midterm: Feb 18"),
46
- ]
47
-
48
- for filename, content in sample_files:
49
- file_path = os.path.join(DOCS_DIR, filename)
50
- if not os.path.exists(file_path):
51
- with open(file_path, 'w', encoding='utf-8') as f:
52
- f.write(content)
53
- logger.info(f"Created sample file: {filename}")
54
-
55
- # Initialize sample documents
56
- add_sample_documents()
57
-
58
- # Register all tools
59
- logger.info(f"Registering document tools with directory: {DOCS_DIR}")
60
- register_document_tools(mcp, DOCS_DIR, UPLOAD_PASSCODE)
61
-
62
- logger.info(f"Registering calendar tools with database: {DB_PATH}")
63
- register_calendar_tools(mcp, DB_PATH)
64
-
65
- # Add server information tool
66
- @mcp.tool()
67
- def get_server_info() -> dict:
68
- """Get information about the MCP server and available tools"""
69
- return {
70
- "name": "Exam Document Hub MCP Server",
71
- "version": "1.0.0",
72
- "status": "running",
73
- "protocol": "HTTPS",
74
- "configuration": {
75
- "documents_directory": DOCS_DIR,
76
- "database_path": DB_PATH,
77
- "port": MCP_PORT
78
- },
79
- "available_tools": {
80
- "document_tools": [
81
- "list_all_documents",
82
- "get_folder_structure",
83
- "search_documents",
84
- "extract_pdf_text",
85
- "extract_docx_text",
86
- "read_text_file",
87
- "search_in_document",
88
- "get_document_summary",
89
- "upload_file (requires passcode)",
90
- "create_folder (requires passcode)",
91
- "delete_file (requires passcode)"
92
- ],
93
- "calendar_tools": [
94
- "get_all_exams",
95
- "get_exams_this_week",
96
- "get_exams_next_week",
97
- "get_exams_by_month",
98
- "find_exam_by_subject",
99
- "get_exams_by_date",
100
- "get_upcoming_exams",
101
- "search_related_documents",
102
- "get_study_schedule",
103
- "get_exam_statistics"
104
- ]
105
- },
106
- "tips": [
107
- "Use 'list_all_documents' to see available files",
108
- "Use 'get_upcoming_exams' to check what's coming up",
109
- "Use 'search_related_documents' to find study materials for a subject",
110
- "Upload operations require a passcode for security"
111
- ]
112
- }
113
-
114
- if __name__ == "__main__":
115
- # Run the MCP server with HTTPS
116
- logger.info(f"Starting Exam Document Hub MCP Server on HTTPS port {MCP_PORT}")
117
- logger.info(f"Documents directory: {DOCS_DIR}")
118
- logger.info(f"Database path: {DB_PATH}")
119
-
120
- # For HTTPS, we need to get the app and run it with uvicorn directly
121
- app = mcp.get_app()
122
-
123
- # Run with SSL
124
- uvicorn.run(
125
- app,
126
- host="0.0.0.0",
127
- port=MCP_PORT,
128
- ssl_keyfile="server.key",
129
- ssl_certfile="server.crt"
130
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/mcp_server_wrapper.py DELETED
@@ -1,28 +0,0 @@
1
- """
2
- MCP Server Wrapper for Claude Desktop
3
- Handles proper startup and error reporting
4
- """
5
-
6
- import sys
7
- import os
8
- import logging
9
-
10
- # Set up proper paths
11
- backend_dir = os.path.dirname(os.path.abspath(__file__))
12
- root_dir = os.path.dirname(backend_dir)
13
- sys.path.insert(0, backend_dir)
14
-
15
- # Change to backend directory for relative imports
16
- os.chdir(backend_dir)
17
-
18
- # Now import and run the MCP server
19
- try:
20
- from mcp_server import mcp
21
-
22
- # Run with stdio transport for Claude Desktop
23
- print("Starting Exam Hub MCP Server for Claude Desktop...", file=sys.stderr)
24
- mcp.run(transport="stdio")
25
-
26
- except Exception as e:
27
- print(f"Error starting MCP server: {e}", file=sys.stderr)
28
- sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/tools/flutter_tools.py DELETED
@@ -1,327 +0,0 @@
1
- """
2
- Flutter App Tools for Reuben OS MCP Server
3
- Provides tools for creating, retrieving, and managing Flutter apps
4
- """
5
-
6
- import os
7
- import json
8
- from pathlib import Path
9
- from typing import Dict, Any, Optional, List
10
- import datetime
11
-
12
- def register_flutter_tools(mcp, data_dir: str, upload_passcode: str):
13
- """Register Flutter app related MCP tools"""
14
-
15
- # Create flutter_apps directory
16
- flutter_apps_dir = os.path.join(data_dir, "documents", "flutter_apps")
17
- os.makedirs(flutter_apps_dir, exist_ok=True)
18
-
19
- def validate_passcode(passcode: str) -> bool:
20
- """Validate the upload passcode"""
21
- return passcode == upload_passcode
22
-
23
- @mcp.tool()
24
- def create_flutter_app(
25
- name: str,
26
- dart_code: str,
27
- dependencies: Optional[List[str]] = None,
28
- pubspec_yaml: Optional[str] = None,
29
- passcode: str = ""
30
- ) -> Dict[str, Any]:
31
- """
32
- Create a new Flutter app and save it to storage
33
-
34
- Args:
35
- name: Name of the Flutter app (will be used as filename)
36
- dart_code: The Dart/Flutter code (typically main.dart content)
37
- dependencies: List of dependencies (e.g., ["http: ^0.13.0", "provider: ^6.0.0"])
38
- pubspec_yaml: Complete pubspec.yaml content (optional)
39
- passcode: Upload passcode for authentication
40
-
41
- Returns:
42
- Dictionary with success status and file path
43
- """
44
- # Validate passcode
45
- if not validate_passcode(passcode):
46
- return {
47
- "status": "error",
48
- "message": "Invalid passcode"
49
- }
50
-
51
- # Validate inputs
52
- if not name or not dart_code:
53
- return {
54
- "status": "error",
55
- "message": "Name and dart_code are required"
56
- }
57
-
58
- # Sanitize name
59
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
60
- if not safe_name:
61
- return {
62
- "status": "error",
63
- "message": "Invalid app name"
64
- }
65
-
66
- # Generate default pubspec.yaml if not provided
67
- if not pubspec_yaml:
68
- dep_lines = []
69
- if dependencies:
70
- for dep in dependencies:
71
- dep_lines.append(f" {dep}")
72
-
73
- deps_section = "\n".join(dep_lines) if dep_lines else ""
74
- pubspec_yaml = f"""name: {safe_name}
75
- description: A Flutter application created via Reuben OS
76
- version: 1.0.0
77
-
78
- environment:
79
- sdk: '>=3.0.0 <4.0.0'
80
-
81
- dependencies:
82
- flutter:
83
- sdk: flutter
84
- {deps_section}
85
-
86
- flutter:
87
- uses-material-design: true
88
- """
89
-
90
- # Create Flutter app object
91
- flutter_app = {
92
- "type": "flutter_app",
93
- "name": safe_name,
94
- "dartCode": dart_code,
95
- "dependencies": dependencies or [],
96
- "pubspecYaml": pubspec_yaml,
97
- "metadata": {
98
- "created": datetime.datetime.now().isoformat(),
99
- "modified": datetime.datetime.now().isoformat(),
100
- "author": "claude"
101
- }
102
- }
103
-
104
- # Save to file
105
- filename = f"{safe_name}.flutter.json"
106
- file_path = os.path.join(flutter_apps_dir, filename)
107
-
108
- # Check if file exists
109
- if os.path.exists(file_path):
110
- return {
111
- "status": "error",
112
- "message": f"Flutter app '{safe_name}' already exists. Use update_flutter_app to modify it."
113
- }
114
-
115
- try:
116
- with open(file_path, 'w', encoding='utf-8') as f:
117
- json.dump(flutter_app, f, indent=2, ensure_ascii=False)
118
-
119
- return {
120
- "status": "success",
121
- "message": f"Flutter app '{safe_name}' created successfully",
122
- "app_name": safe_name,
123
- "file_path": file_path,
124
- "app_data": flutter_app
125
- }
126
- except Exception as e:
127
- return {
128
- "status": "error",
129
- "message": f"Failed to save Flutter app: {str(e)}"
130
- }
131
-
132
- @mcp.tool()
133
- def get_flutter_app(name: str) -> Dict[str, Any]:
134
- """
135
- Retrieve a Flutter app by name
136
-
137
- Args:
138
- name: Name of the Flutter app
139
-
140
- Returns:
141
- Dictionary with app data or error
142
- """
143
- # Sanitize name
144
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
145
- filename = f"{safe_name}.flutter.json"
146
- file_path = os.path.join(flutter_apps_dir, filename)
147
-
148
- if not os.path.exists(file_path):
149
- return {
150
- "status": "error",
151
- "message": f"Flutter app '{safe_name}' not found"
152
- }
153
-
154
- try:
155
- with open(file_path, 'r', encoding='utf-8') as f:
156
- app_data = json.load(f)
157
-
158
- return {
159
- "status": "success",
160
- "app_name": safe_name,
161
- "app_data": app_data
162
- }
163
- except Exception as e:
164
- return {
165
- "status": "error",
166
- "message": f"Failed to read Flutter app: {str(e)}"
167
- }
168
-
169
- @mcp.tool()
170
- def list_flutter_apps() -> Dict[str, Any]:
171
- """
172
- List all available Flutter apps
173
-
174
- Returns:
175
- Dictionary with list of Flutter apps
176
- """
177
- try:
178
- apps = []
179
-
180
- if not os.path.exists(flutter_apps_dir):
181
- return {
182
- "status": "success",
183
- "apps": [],
184
- "count": 0
185
- }
186
-
187
- for filename in os.listdir(flutter_apps_dir):
188
- if filename.endswith('.flutter.json'):
189
- file_path = os.path.join(flutter_apps_dir, filename)
190
- try:
191
- with open(file_path, 'r', encoding='utf-8') as f:
192
- app_data = json.load(f)
193
-
194
- apps.append({
195
- "name": app_data.get("name", filename.replace('.flutter.json', '')),
196
- "created": app_data.get("metadata", {}).get("created"),
197
- "modified": app_data.get("metadata", {}).get("modified"),
198
- "dependencies_count": len(app_data.get("dependencies", [])),
199
- "file_path": file_path
200
- })
201
- except Exception as e:
202
- # Skip malformed files
203
- continue
204
-
205
- return {
206
- "status": "success",
207
- "apps": apps,
208
- "count": len(apps)
209
- }
210
- except Exception as e:
211
- return {
212
- "status": "error",
213
- "message": f"Failed to list Flutter apps: {str(e)}"
214
- }
215
-
216
- @mcp.tool()
217
- def update_flutter_app(
218
- name: str,
219
- dart_code: Optional[str] = None,
220
- dependencies: Optional[List[str]] = None,
221
- pubspec_yaml: Optional[str] = None,
222
- passcode: str = ""
223
- ) -> Dict[str, Any]:
224
- """
225
- Update an existing Flutter app
226
-
227
- Args:
228
- name: Name of the Flutter app to update
229
- dart_code: New Dart/Flutter code (optional, keeps existing if not provided)
230
- dependencies: New dependencies list (optional, keeps existing if not provided)
231
- pubspec_yaml: New pubspec.yaml content (optional, keeps existing if not provided)
232
- passcode: Upload passcode for authentication
233
-
234
- Returns:
235
- Dictionary with success status
236
- """
237
- # Validate passcode
238
- if not validate_passcode(passcode):
239
- return {
240
- "status": "error",
241
- "message": "Invalid passcode"
242
- }
243
-
244
- # Sanitize name
245
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
246
- filename = f"{safe_name}.flutter.json"
247
- file_path = os.path.join(flutter_apps_dir, filename)
248
-
249
- if not os.path.exists(file_path):
250
- return {
251
- "status": "error",
252
- "message": f"Flutter app '{safe_name}' not found"
253
- }
254
-
255
- try:
256
- # Read existing app
257
- with open(file_path, 'r', encoding='utf-8') as f:
258
- app_data = json.load(f)
259
-
260
- # Update fields if provided
261
- if dart_code is not None:
262
- app_data["dartCode"] = dart_code
263
- if dependencies is not None:
264
- app_data["dependencies"] = dependencies
265
- if pubspec_yaml is not None:
266
- app_data["pubspecYaml"] = pubspec_yaml
267
-
268
- # Update metadata
269
- app_data["metadata"]["modified"] = datetime.datetime.now().isoformat()
270
-
271
- # Save updated app
272
- with open(file_path, 'w', encoding='utf-8') as f:
273
- json.dump(app_data, f, indent=2, ensure_ascii=False)
274
-
275
- return {
276
- "status": "success",
277
- "message": f"Flutter app '{safe_name}' updated successfully",
278
- "app_data": app_data
279
- }
280
- except Exception as e:
281
- return {
282
- "status": "error",
283
- "message": f"Failed to update Flutter app: {str(e)}"
284
- }
285
-
286
- @mcp.tool()
287
- def delete_flutter_app(name: str, passcode: str) -> Dict[str, Any]:
288
- """
289
- Delete a Flutter app
290
-
291
- Args:
292
- name: Name of the Flutter app to delete
293
- passcode: Upload passcode for authentication
294
-
295
- Returns:
296
- Dictionary with success status
297
- """
298
- # Validate passcode
299
- if not validate_passcode(passcode):
300
- return {
301
- "status": "error",
302
- "message": "Invalid passcode"
303
- }
304
-
305
- # Sanitize name
306
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
307
- filename = f"{safe_name}.flutter.json"
308
- file_path = os.path.join(flutter_apps_dir, filename)
309
-
310
- if not os.path.exists(file_path):
311
- return {
312
- "status": "error",
313
- "message": f"Flutter app '{safe_name}' not found"
314
- }
315
-
316
- try:
317
- os.remove(file_path)
318
- return {
319
- "status": "success",
320
- "message": f"Flutter app '{safe_name}' deleted successfully"
321
- }
322
- except Exception as e:
323
- return {
324
- "status": "error",
325
- "message": f"Failed to delete Flutter app: {str(e)}"
326
- }
327
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mcp-server.js CHANGED
@@ -182,6 +182,96 @@ class ReubenOSMCPServer {
182
  required: ['sessionKey', 'fileName'],
183
  },
184
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  ],
186
  }));
187
 
@@ -202,6 +292,16 @@ class ReubenOSMCPServer {
202
  return await this.generateDocument(args);
203
  case 'process_document':
204
  return await this.processDocument(args);
 
 
 
 
 
 
 
 
 
 
205
  default:
206
  throw new Error(`Unknown tool: ${name}`);
207
  }
@@ -389,6 +489,127 @@ class ReubenOSMCPServer {
389
  };
390
  }
391
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  async run() {
393
  const transport = new StdioServerTransport();
394
  await this.server.connect(transport);
 
182
  required: ['sessionKey', 'fileName'],
183
  },
184
  },
185
+ {
186
+ name: 'create_flutter_app',
187
+ description: 'Create a new Flutter app with Dart code that can be run in Zapp',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ name: {
192
+ type: 'string',
193
+ description: 'Name of the Flutter app (alphanumeric, hyphens, underscores)',
194
+ },
195
+ dartCode: {
196
+ type: 'string',
197
+ description: 'Complete Dart/Flutter code (typically main.dart content)',
198
+ },
199
+ dependencies: {
200
+ type: 'array',
201
+ items: { type: 'string' },
202
+ description: 'List of dependencies (e.g., ["http: ^0.13.0", "provider: ^6.0.0"])',
203
+ },
204
+ pubspecYaml: {
205
+ type: 'string',
206
+ description: 'Complete pubspec.yaml content (optional, will be auto-generated)',
207
+ },
208
+ },
209
+ required: ['name', 'dartCode'],
210
+ },
211
+ },
212
+ {
213
+ name: 'get_flutter_app',
214
+ description: 'Retrieve a Flutter app by name',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ name: {
219
+ type: 'string',
220
+ description: 'Name of the Flutter app',
221
+ },
222
+ },
223
+ required: ['name'],
224
+ },
225
+ },
226
+ {
227
+ name: 'list_flutter_apps',
228
+ description: 'List all available Flutter apps',
229
+ inputSchema: {
230
+ type: 'object',
231
+ properties: {},
232
+ },
233
+ },
234
+ {
235
+ name: 'update_flutter_app',
236
+ description: 'Update an existing Flutter app',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ name: {
241
+ type: 'string',
242
+ description: 'Name of the Flutter app to update',
243
+ },
244
+ dartCode: {
245
+ type: 'string',
246
+ description: 'New Dart/Flutter code (optional)',
247
+ },
248
+ dependencies: {
249
+ type: 'array',
250
+ items: { type: 'string' },
251
+ description: 'New dependencies list (optional)',
252
+ },
253
+ pubspecYaml: {
254
+ type: 'string',
255
+ description: 'New pubspec.yaml content (optional)',
256
+ },
257
+ },
258
+ required: ['name'],
259
+ },
260
+ },
261
+ {
262
+ name: 'delete_flutter_app',
263
+ description: 'Delete a Flutter app',
264
+ inputSchema: {
265
+ type: 'object',
266
+ properties: {
267
+ name: {
268
+ type: 'string',
269
+ description: 'Name of the Flutter app to delete',
270
+ },
271
+ },
272
+ required: ['name'],
273
+ },
274
+ },
275
  ],
276
  }));
277
 
 
292
  return await this.generateDocument(args);
293
  case 'process_document':
294
  return await this.processDocument(args);
295
+ case 'create_flutter_app':
296
+ return await this.createFlutterApp(args);
297
+ case 'get_flutter_app':
298
+ return await this.getFlutterApp(args);
299
+ case 'list_flutter_apps':
300
+ return await this.listFlutterApps(args);
301
+ case 'update_flutter_app':
302
+ return await this.updateFlutterApp(args);
303
+ case 'delete_flutter_app':
304
+ return await this.deleteFlutterApp(args);
305
  default:
306
  throw new Error(`Unknown tool: ${name}`);
307
  }
 
489
  };
490
  }
491
 
492
+ async createFlutterApp(args) {
493
+ const response = await fetch(`${BASE_URL}/api/flutter/create`, {
494
+ method: 'POST',
495
+ headers: { 'Content-Type': 'application/json' },
496
+ body: JSON.stringify({
497
+ name: args.name,
498
+ dartCode: args.dartCode,
499
+ dependencies: args.dependencies || [],
500
+ pubspecYaml: args.pubspecYaml,
501
+ }),
502
+ });
503
+
504
+ const data = await response.json();
505
+ return {
506
+ content: [
507
+ {
508
+ type: 'text',
509
+ text: data.success
510
+ ? `βœ… Flutter app "${data.appName}" created successfully!\n\nπŸ“± App Details:\n- Name: ${data.appName}\n- Dependencies: ${data.dependencies?.length || 0}\n- File: ${data.fileName}\n\n🎯 Next Steps:\n1. Open ReubenOS File Manager\n2. Navigate to flutter_apps folder\n3. Double-click "${data.appName}" to open in Zapp runner\n4. Copy the code and paste into Zapp's lib/main.dart\n5. Add dependencies to pubspec.yaml\n6. Click Run in Zapp!\n\nπŸš€ Your Flutter app is ready to test!`
511
+ : `❌ Failed to create Flutter app: ${data.error}`,
512
+ },
513
+ ],
514
+ };
515
+ }
516
+
517
+ async getFlutterApp(args) {
518
+ const response = await fetch(`${BASE_URL}/api/flutter/get?name=${encodeURIComponent(args.name)}`);
519
+ const data = await response.json();
520
+
521
+ if (data.success) {
522
+ return {
523
+ content: [
524
+ {
525
+ type: 'text',
526
+ text: `Flutter App: ${data.appData.name}\n\nDart Code:\n${data.appData.dartCode}\n\nDependencies:\n${data.appData.dependencies?.join('\n') || 'None'}\n\nCreated: ${data.appData.metadata?.created || 'Unknown'}`,
527
+ },
528
+ ],
529
+ };
530
+ } else {
531
+ return {
532
+ content: [
533
+ {
534
+ type: 'text',
535
+ text: `Failed to get Flutter app: ${data.error}`,
536
+ },
537
+ ],
538
+ };
539
+ }
540
+ }
541
+
542
+ async listFlutterApps(args) {
543
+ const response = await fetch(`${BASE_URL}/api/flutter/list`);
544
+ const data = await response.json();
545
+
546
+ if (data.success) {
547
+ const appList = data.apps
548
+ .map((app) => `πŸ“± ${app.name} (${app.dependenciesCount} deps) - Created: ${new Date(app.created).toLocaleDateString()}`)
549
+ .join('\n');
550
+ return {
551
+ content: [
552
+ {
553
+ type: 'text',
554
+ text: `Flutter Apps (${data.count} total):\n\n${appList || 'No Flutter apps found'}`,
555
+ },
556
+ ],
557
+ };
558
+ } else {
559
+ return {
560
+ content: [
561
+ {
562
+ type: 'text',
563
+ text: `Failed to list Flutter apps: ${data.error}`,
564
+ },
565
+ ],
566
+ };
567
+ }
568
+ }
569
+
570
+ async updateFlutterApp(args) {
571
+ const response = await fetch(`${BASE_URL}/api/flutter/update`, {
572
+ method: 'PUT',
573
+ headers: { 'Content-Type': 'application/json' },
574
+ body: JSON.stringify({
575
+ name: args.name,
576
+ dartCode: args.dartCode,
577
+ dependencies: args.dependencies,
578
+ pubspecYaml: args.pubspecYaml,
579
+ }),
580
+ });
581
+
582
+ const data = await response.json();
583
+ return {
584
+ content: [
585
+ {
586
+ type: 'text',
587
+ text: data.success
588
+ ? `βœ… Flutter app "${data.appName}" updated successfully!`
589
+ : `❌ Failed to update Flutter app: ${data.error}`,
590
+ },
591
+ ],
592
+ };
593
+ }
594
+
595
+ async deleteFlutterApp(args) {
596
+ const response = await fetch(`${BASE_URL}/api/flutter/delete?name=${encodeURIComponent(args.name)}`, {
597
+ method: 'DELETE',
598
+ });
599
+
600
+ const data = await response.json();
601
+ return {
602
+ content: [
603
+ {
604
+ type: 'text',
605
+ text: data.success
606
+ ? `βœ… Flutter app "${args.name}" deleted successfully!`
607
+ : `❌ Failed to delete Flutter app: ${data.error}`,
608
+ },
609
+ ],
610
+ };
611
+ }
612
+
613
  async run() {
614
  const transport = new StdioServerTransport();
615
  await this.server.connect(transport);