-
Notifications
You must be signed in to change notification settings - Fork 0
Add remote MCP server support with settings UI #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5e021e0
385de84
f537004
2281975
f0651a2
4996618
df94f6e
a360b6b
669318b
34b3dba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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> | ||
| } |
| 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, | ||||||||||||||||||||
|
Comment on lines
+42
to
+43
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| /** Whether the server is enabled */ | ||||||||||||||||||||
| val isEnabled: Boolean = true, | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** Last error message if connection failed */ | ||||||||||||||||||||
| val lastError: String? = null, | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+48
to
+50
|
||||||||||||||||||||
| /** Last error message if connection failed */ | |
| val lastError: String? = null, | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using System.currentTimeMillis() as a default value for createdAt and updatedAt in a data class can be problematic. Every time an McpServer object is copied without explicitly providing these values, they will be reset to the current time. It's safer to remove these defaults from the data class and set them explicitly only when creating or updating an entity in the repository. This prevents accidental timestamp changes.
| val createdAt: Long = System.currentTimeMillis(), | |
| /** Timestamp when the server was last modified */ | |
| val updatedAt: Long = System.currentTimeMillis(), | |
| /** Timestamp when the server was added */ | |
| val createdAt: Long, | |
| /** Timestamp when the server was last modified */ | |
| val updatedAt: Long, |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The customHeadersJson field is defined but not exposed in the UI or used in the service layer. Either implement support for custom headers in the HTTP requests or remove this unused field to keep the data model simple and maintainable.
| val description: String = "", | |
| /** Custom headers as JSON string (e.g., for additional auth) */ | |
| val customHeadersJson: String? = null | |
| val description: String = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
fallbackToDestructiveMigration()can be risky in a production application as it will delete the database and all its data if a migration is missing, leading to unexpected data loss for users. For a production-ready app, it's safer to provide all necessary migrations and remove this fallback.