Spaces:
Running
Running
Add Flutter tools to Node.js MCP server and remove Python MCP server
Browse files- app/api/flutter/create/route.ts +101 -0
- app/api/flutter/delete/route.ts +57 -0
- app/api/flutter/get/route.ts +50 -0
- app/api/flutter/list/route.ts +58 -0
- app/api/flutter/update/route.ts +68 -0
- backend/mcp_server.py +0 -150
- backend/mcp_server_https.py +0 -130
- backend/mcp_server_wrapper.py +0 -28
- backend/tools/flutter_tools.py +0 -327
- mcp-server.js +221 -0
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);
|