Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5e021e0
Initial plan
Copilot Jan 17, 2026
385de84
Add Remote MCP Server support with UI
Copilot Jan 17, 2026
f537004
Address code review feedback - fix hardcoded values and security impr…
Copilot Jan 17, 2026
2281975
Fix DAO default parameters and explicit timestamp passing
Copilot Jan 17, 2026
f0651a2
Fix MCP SSE support for Zapier MCP servers and build errors
Copilot Jan 17, 2026
4996618
Improve SSE parsing to correctly handle event format per SSE spec
Copilot Jan 17, 2026
df94f6e
Add proper Streamable HTTP transport support alongside SSE
Copilot Jan 17, 2026
a360b6b
Fix code review comments on transport documentation
Copilot Jan 17, 2026
669318b
Address code review feedback and add GitHub workflow for debug APK
Copilot Jan 17, 2026
34b3dba
Fix race condition in error auto-clearing
Copilot Jan 17, 2026
5f3ea4c
Merge pull request #1 from Godzilla675/copilot/add-remote-mcp-server-…
Godzilla675 Jan 17, 2026
aac348a
Merge branch 'Siddhesh2377:re-write' into re-write
Godzilla675 Jan 17, 2026
dd31ae5
Initial plan
Copilot Jan 17, 2026
da0af0c
Enable BuildConfig for memory-vault
Copilot Jan 17, 2026
18aea53
Merge pull request #2 from Godzilla675/copilot/fix-debug-workflow-build
Godzilla675 Jan 17, 2026
c2e79bf
Initial plan
Copilot Jan 17, 2026
148d979
Integrate MCP tools with GGUF
Copilot Jan 17, 2026
89dde9b
Refine MCP tool call execution
Copilot Jan 17, 2026
524d6eb
Merge pull request #3 from Godzilla675/copilot/test-ai-models-access
Godzilla675 Jan 17, 2026
03cfc52
Initial plan
Copilot Jan 17, 2026
d8692ff
Add MCP server integration tests
Copilot Jan 17, 2026
8872229
Fix MCP integration test input schemas to include required instructio…
Copilot Jan 17, 2026
cdb24df
Fix Streamable HTTP transport and improve MCP server tests
Copilot Jan 17, 2026
a87425a
Merge pull request #5 from Godzilla675/copilot/connect-ai-model-to-mc…
Godzilla675 Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/build-debug-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Build Debug APK

on:
pull_request:
branches: [ main, master ]
workflow_dispatch:

jobs:
build:
name: Build Debug APK
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build Debug APK
run: ./gradlew assembleDebug --no-daemon

- name: Upload Debug APK
uses: actions/upload-artifact@v4
with:
name: app-debug
path: app/build/outputs/apk/debug/app-debug.apk
retention-days: 14
6 changes: 5 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ dependencies {

// Debug
debugImplementation(libs.androidx.compose.ui.tooling)

// Tests
testImplementation(libs.junit)
testImplementation(libs.org.json)
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The org.json dependency is being added for testing, but this library is already included in the Android SDK. This creates redundant dependencies and potential version conflicts. Consider removing this dependency since org.json is available by default in Android.

Suggested change
testImplementation(libs.org.json)

Copilot uses AI. Check for mistakes.
}

fun getProperty(value: String): String {
Expand All @@ -154,4 +158,4 @@ fun getProperty(value: String): String {
} else {
System.getenv(value) ?: "\"sample_val\""
}
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/dark/tool_neuron/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.dark.tool_neuron.data.TermsDataStore
import com.dark.tool_neuron.di.AppContainer
import com.dark.tool_neuron.engine.EmbeddingEngine
import com.dark.tool_neuron.ui.screen.EmbeddingSetupScreen
import com.dark.tool_neuron.ui.screen.McpServersScreen
import com.dark.tool_neuron.ui.screen.ModelConfigEditorScreen
import com.dark.tool_neuron.ui.screen.ModelStoreScreen
import com.dark.tool_neuron.ui.screen.TermsAndConditionsScreen
Expand Down Expand Up @@ -121,6 +122,7 @@ sealed class Screen(val route: String) {
object Store : Screen("store")
object Editor : Screen("editor")
object VaultManager: Screen("vault_manager")
object McpServers: Screen("mcp_servers")
}

@Composable
Expand Down Expand Up @@ -176,6 +178,9 @@ fun AppNavigation(
onVaultManagerClick = {
navController.navigate(Screen.VaultManager.route)
},
onMcpServersClick = {
navController.navigate(Screen.McpServers.route)
},
chatViewModel = chatViewModel,
llmModelViewModel = llmModelViewModel
)
Expand All @@ -196,5 +201,11 @@ fun AppNavigation(
composable(Screen.VaultManager.route) {
VaultDashboard()
}

composable(Screen.McpServers.route) {
McpServersScreen(onBackClick = {
navController.popBackStack()
})
}
}
}
31 changes: 28 additions & 3 deletions app/src/main/java/com/dark/tool_neuron/database/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,27 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.dark.tool_neuron.database.dao.McpServerDao
import com.dark.tool_neuron.database.dao.ModelConfigDao
import com.dark.tool_neuron.database.dao.ModelDao
import com.dark.tool_neuron.database.dao.RagDao
import com.dark.tool_neuron.models.converters.Converters
import com.dark.tool_neuron.models.table_schema.InstalledRag
import com.dark.tool_neuron.models.table_schema.McpServer
import com.dark.tool_neuron.models.table_schema.Model
import com.dark.tool_neuron.models.table_schema.ModelConfig

@Database(
entities = [Model::class, ModelConfig::class, InstalledRag::class],
version = 4,
entities = [Model::class, ModelConfig::class, InstalledRag::class, McpServer::class],
version = 5,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun modelDao(): ModelDao
abstract fun modelConfigDao(): ModelConfigDao
abstract fun ragDao(): RagDao
abstract fun mcpServerDao(): McpServerDao

companion object {
@Volatile
Expand Down Expand Up @@ -114,14 +117,36 @@ abstract class AppDatabase : RoomDatabase() {
}
}

private val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(db: SupportSQLiteDatabase) {
// Create mcp_servers table
db.execSQL("""
CREATE TABLE IF NOT EXISTS mcp_servers (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
transportType TEXT NOT NULL,
apiKey TEXT,
isEnabled INTEGER NOT NULL,
lastError TEXT,
createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL,
lastConnectedAt INTEGER,
description TEXT NOT NULL,
customHeadersJson TEXT
)
""".trimIndent())
}
}

fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"llm_models_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.dark.tool_neuron.database.dao

import androidx.room.*
import com.dark.tool_neuron.models.table_schema.McpServer
import kotlinx.coroutines.flow.Flow

@Dao
interface McpServerDao {

@Query("SELECT * FROM mcp_servers ORDER BY name ASC")
fun getAllServers(): Flow<List<McpServer>>

@Query("SELECT * FROM mcp_servers WHERE isEnabled = 1 ORDER BY name ASC")
fun getEnabledServers(): Flow<List<McpServer>>

@Query("SELECT * FROM mcp_servers WHERE id = :id")
suspend fun getServerById(id: String): McpServer?

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertServer(server: McpServer)

@Update
suspend fun updateServer(server: McpServer)

@Delete
suspend fun deleteServer(server: McpServer)

@Query("DELETE FROM mcp_servers WHERE id = :id")
suspend fun deleteServerById(id: String)

@Query("UPDATE mcp_servers SET isEnabled = :isEnabled, updatedAt = :updatedAt WHERE id = :id")
suspend fun updateServerEnabled(id: String, isEnabled: Boolean, updatedAt: Long)

@Query("UPDATE mcp_servers SET lastConnectedAt = :timestamp, updatedAt = :updatedAt WHERE id = :id")
suspend fun updateLastConnected(id: String, timestamp: Long, updatedAt: Long)

@Query("SELECT COUNT(*) FROM mcp_servers")
fun getServerCount(): Flow<Int>

@Query("SELECT COUNT(*) FROM mcp_servers WHERE isEnabled = 1")
fun getEnabledServerCount(): Flow<Int>
}
15 changes: 13 additions & 2 deletions app/src/main/java/com/dark/tool_neuron/di/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import android.app.Application
import android.content.Context
import com.dark.tool_neuron.database.AppDatabase
import com.dark.tool_neuron.repo.ChatRepository
import com.dark.tool_neuron.repo.McpServerRepository
import com.dark.tool_neuron.repo.ModelRepository
import com.dark.tool_neuron.service.McpClientService
import com.dark.tool_neuron.vault.VaultHelper
import com.dark.tool_neuron.viewmodel.factory.ChatListViewModelFactory
import com.dark.tool_neuron.viewmodel.factory.ChatViewModelFactory
Expand All @@ -21,6 +23,8 @@ object AppContainer {
private lateinit var database: AppDatabase
private lateinit var modelRepository: ModelRepository
private lateinit var chatRepository: ChatRepository
private lateinit var mcpServerRepository: McpServerRepository
private lateinit var mcpClientService: McpClientService
private lateinit var llmModelViewModelFactory: LLMModelViewModelFactory
private lateinit var chatListViewModelFactory: ChatListViewModelFactory
private lateinit var chatViewModelFactory: ChatViewModelFactory
Expand All @@ -38,10 +42,17 @@ object AppContainer {
)

chatRepository = ChatRepository()
mcpServerRepository = McpServerRepository(database.mcpServerDao())
mcpClientService = McpClientService()

llmModelViewModelFactory = LLMModelViewModelFactory(application, modelRepository)
chatListViewModelFactory = ChatListViewModelFactory(chatManager)
chatViewModelFactory = ChatViewModelFactory(chatManager, generationManager)
chatViewModelFactory = ChatViewModelFactory(
chatManager,
generationManager,
mcpServerRepository,
mcpClientService
)

initVault(context)
}
Expand Down Expand Up @@ -84,4 +95,4 @@ object AppContainer {


fun isVaultReady(): Boolean = vaultInitialized
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/com/dark/tool_neuron/di/HiltModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.dark.tool_neuron.database.AppDatabase
import com.dark.tool_neuron.engine.EmbeddingEngine
import com.dark.tool_neuron.repo.ChatRepository
import com.dark.tool_neuron.repo.McpServerRepository
import com.dark.tool_neuron.repo.ModelRepository
import com.dark.tool_neuron.repo.RagRepository
import com.dark.tool_neuron.service.McpClientService
import com.dark.tool_neuron.worker.ChatManager
import com.dark.tool_neuron.worker.GenerationManager
import com.dark.tool_neuron.worker.RagVaultIntegration
Expand Down Expand Up @@ -65,6 +67,14 @@
context = context
)
}

@Provides
@Singleton
fun provideMcpServerRepository(database: AppDatabase): McpServerRepository {
return McpServerRepository(
mcpServerDao = database.mcpServerDao()
)
}
}

@Module
Expand All @@ -78,6 +88,17 @@
}
}

@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {

@Provides
@Singleton
fun provideMcpClientService(): McpClientService {
return McpClientService()
}
}

@Module
@InstallIn(SingletonComponent::class)
object WorkerModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.dark.tool_neuron.models.converters
import androidx.room.TypeConverter
import com.dark.tool_neuron.models.enums.PathType
import com.dark.tool_neuron.models.enums.ProviderType
import com.dark.tool_neuron.models.table_schema.McpTransportType

class Converters {
@TypeConverter
Expand All @@ -16,4 +17,10 @@ class Converters {

@TypeConverter
fun toPathType(value: String): PathType = PathType.valueOf(value)

@TypeConverter
fun fromMcpTransportType(value: McpTransportType): String = value.name

@TypeConverter
fun toMcpTransportType(value: String): McpTransportType = McpTransportType.valueOf(value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.dark.tool_neuron.models.table_schema

import androidx.room.Entity
import androidx.room.PrimaryKey

/**
* Transport type for MCP server connections
*/
enum class McpTransportType {
SSE, // Server-Sent Events (HTTP)
STREAMABLE_HTTP // Streamable HTTP transport
}

/**
* Connection status of an MCP server (runtime only, not persisted)
*/
enum class McpConnectionStatus {
DISCONNECTED,
CONNECTING,
CONNECTED,
ERROR
}

/**
* Entity representing a remote MCP (Model Context Protocol) server configuration.
* MCP servers provide tools, resources, and prompts to LLM applications.
*/
@Entity(tableName = "mcp_servers")
data class McpServer(
@PrimaryKey
val id: String,

/** Display name for the server */
val name: String,

/** Server URL (e.g., "https://api.example.com/mcp") */
val url: String,

/** Transport type for the connection */
val transportType: McpTransportType = McpTransportType.SSE,

/** Optional API key for authentication */
val apiKey: String? = null,

/** Whether the server is enabled */
val isEnabled: Boolean = true,

/** Last error message if connection failed */
val lastError: String? = null,

/** Timestamp when the server was added */
val createdAt: Long = System.currentTimeMillis(),

/** Timestamp when the server was last modified */
val updatedAt: Long = System.currentTimeMillis(),

/** Timestamp when last successfully connected */
val lastConnectedAt: Long? = null,

/** Optional description */
val description: String = "",

/** Custom headers as JSON string (e.g., for additional auth) */
val customHeadersJson: String? = null
) {
companion object {
fun generateId(): String = java.util.UUID.randomUUID().toString()
}
}
Loading
Loading