feat: ticket ingestion MCP server (#1)
* basic ticket uploading * take project_id in request params instead of env * add an endpoint to list all available projects * add mcp server bin to npx * add missing scripts to package and publish to npm * fix rmcp version * Use utils::asset_dir * Don't run migrations or create DB from MCP * a fix for the first dev run when no frontend/dist/index.html exists * Add more MCP endpoints (#8) * add new endpoints for project and task management * add simpler more focused endpoints to improve agent understanding on this MCP * improve test script * combine npm binaries and allow passing --mcp as an arg * cargo fmt * fixes after rebase * clippy fixes * Script tweaks --------- Co-authored-by: couscous <couscous@runner.com> Co-authored-by: anastasiya1155 <anastasiya1155@gmail.com> Co-authored-by: Louis Knight-Webb <louis@bloop.ai> Co-authored-by: Anastasiia Solop <35258279+anastasiya1155@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
58f621c816
commit
0514d437a2
56
backend/.sqlx/query-7193dead2b112b137880482fe8e8c822c67ef6692e0456683331a438a4aa002f.json
generated
Normal file
56
backend/.sqlx/query-7193dead2b112b137880482fe8e8c822c67ef6692e0456683331a438a4aa002f.json
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\", project_id as \"project_id!: Uuid\", title, description, status as \"status!: TaskStatus\", created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\"\n FROM tasks \n WHERE project_id = $1 AND title = $2\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!: Uuid",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "project_id!: Uuid",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status!: TaskStatus",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7193dead2b112b137880482fe8e8c822c67ef6692e0456683331a438a4aa002f"
|
||||
}
|
||||
@@ -3,6 +3,7 @@ name = "vibe-kanban"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
default-run = "vibe-kanban"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "vibe_kanban"
|
||||
@@ -35,6 +36,8 @@ open = "5.3.2"
|
||||
ignore = "0.4"
|
||||
command-group = { version = "5.0", features = ["with-tokio"] }
|
||||
openssl-sys = { workspace = true }
|
||||
rmcp = { version = "0.1.5", features = ["server", "transport-io"] }
|
||||
schemars = "0.8"
|
||||
|
||||
[build-dependencies]
|
||||
ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] }
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn main() {
|
||||
// Tell cargo to rerun build script if models change
|
||||
println!("cargo:rerun-if-changed=src/models/");
|
||||
// Create frontend/dist directory if it doesn't exist
|
||||
let dist_path = Path::new("../frontend/dist");
|
||||
if !dist_path.exists() {
|
||||
println!("cargo:warning=Creating dummy frontend/dist directory for compilation");
|
||||
fs::create_dir_all(dist_path).unwrap();
|
||||
|
||||
// Create a dummy index.html
|
||||
let dummy_html = r#"<!DOCTYPE html>
|
||||
<html><head><title>Build frontend first</title></head>
|
||||
<body><h1>Please build the frontend</h1></body></html>"#;
|
||||
|
||||
fs::write(dist_path.join("index.html"), dummy_html).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
34
backend/src/bin/mcp_task_server.rs
Normal file
34
backend/src/bin/mcp_task_server.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use rmcp::{transport::stdio, ServiceExt};
|
||||
use sqlx::{sqlite::SqliteConnectOptions, SqlitePool};
|
||||
use vibe_kanban::{mcp::task_server::TaskServer, utils::asset_dir};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter("debug")
|
||||
.with_writer(std::io::stderr)
|
||||
.init();
|
||||
|
||||
tracing::debug!("[MCP] Starting MCP task server...");
|
||||
|
||||
// Database connection
|
||||
let database_url = format!(
|
||||
"sqlite://{}",
|
||||
asset_dir().join("db.sqlite").to_string_lossy()
|
||||
);
|
||||
|
||||
let options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(false);
|
||||
let pool = SqlitePool::connect_with(options).await?;
|
||||
|
||||
let service = TaskServer::new(pool)
|
||||
.serve(stdio())
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
tracing::error!("serving error: {:?}", e);
|
||||
})?;
|
||||
|
||||
service.waiting().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod app_state;
|
||||
pub mod execution_monitor;
|
||||
pub mod executor;
|
||||
pub mod executors;
|
||||
pub mod mcp;
|
||||
pub mod models;
|
||||
pub mod routes;
|
||||
pub mod utils;
|
||||
|
||||
@@ -17,6 +17,7 @@ mod app_state;
|
||||
mod execution_monitor;
|
||||
mod executor;
|
||||
mod executors;
|
||||
mod mcp;
|
||||
mod models;
|
||||
mod routes;
|
||||
mod utils;
|
||||
|
||||
1
backend/src/mcp/mod.rs
Normal file
1
backend/src/mcp/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod task_server;
|
||||
1333
backend/src/mcp/task_server.rs
Normal file
1333
backend/src/mcp/task_server.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -244,4 +244,22 @@ impl Task {
|
||||
.await?;
|
||||
Ok(result.is_some())
|
||||
}
|
||||
|
||||
pub async fn find_task_by_title(
|
||||
pool: &SqlitePool,
|
||||
project_id: Uuid,
|
||||
title: &str,
|
||||
) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Task,
|
||||
r#"SELECT id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>"
|
||||
FROM tasks
|
||||
WHERE project_id = $1 AND title = $2
|
||||
LIMIT 1"#,
|
||||
project_id,
|
||||
title
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user