1use anyhow::Result;
2use libsql::Connection;
3use serde::{Deserialize, Serialize};
4
5use crate::db;
6
7#[derive(Debug, Deserialize, Serialize)]
10pub struct JsonRpcRequest {
11 pub jsonrpc: String,
12 #[serde(default)]
13 pub id: serde_json::Value,
14 pub method: String,
15 pub params: Option<serde_json::Value>,
16}
17
18#[derive(Debug, Serialize)]
19pub struct JsonRpcResponse {
20 pub jsonrpc: String,
21 pub id: serde_json::Value,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub result: Option<serde_json::Value>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub error: Option<serde_json::Value>,
26}
27
28#[derive(Debug, Deserialize, Serialize, Clone)]
31#[serde(rename_all = "camelCase")]
32pub struct EntityInput {
33 pub name: String,
34 pub entity_type: String,
35 pub observations: Vec<String>,
36}
37
38#[derive(Debug, Deserialize, Serialize, Clone)]
39#[serde(rename_all = "camelCase")]
40pub struct RelationInput {
41 pub from: String,
42 pub to: String,
43 pub relation_type: String,
44}
45
46#[derive(Debug, Deserialize, Clone)]
47#[serde(rename_all = "camelCase")]
48pub struct ObservationInput {
49 pub entity_name: String,
50 pub contents: Vec<String>,
51}
52
53#[derive(Debug, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct ObservationDeletion {
56 pub entity_name: String,
57 pub observations: Vec<String>,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone)]
61#[serde(rename_all = "camelCase")]
62pub struct EntityOutput {
63 pub name: String,
64 pub entity_type: String,
65 #[serde(skip_serializing_if = "Vec::is_empty", default)]
66 pub observations: Vec<String>,
67 pub truths: std::collections::BTreeMap<String, String>,
68 pub observation_count: usize,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub body: Option<String>,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct Graph {
76 pub entities: Vec<EntityOutput>,
77 pub relations: Vec<RelationInput>,
78}
79
80#[derive(Debug, Deserialize)]
83struct CreateEntitiesParams {
84 entities: Vec<EntityInput>,
85}
86
87#[derive(Debug, Deserialize)]
88struct CreateRelationsParams {
89 relations: Vec<RelationInput>,
90}
91
92#[derive(Debug, Deserialize)]
93struct AddObservationsParams {
94 observations: Vec<ObservationInput>,
95}
96
97#[derive(Debug, Deserialize)]
98#[serde(rename_all = "camelCase")]
99struct DeleteEntitiesParams {
100 entity_names: Vec<String>,
101}
102
103#[derive(Debug, Deserialize)]
104struct DeleteObservationsParams {
105 deletions: Vec<ObservationDeletion>,
106}
107
108#[derive(Debug, Deserialize)]
109struct DeleteRelationsParams {
110 relations: Vec<RelationInput>,
111}
112
113#[derive(Debug, Deserialize)]
114struct SearchNodesParams {
115 query: String,
116 limit: Option<usize>,
117}
118
119#[derive(Debug, Deserialize)]
120struct OpenNodesParams {
121 names: Vec<String>,
122}
123
124#[derive(Debug, Deserialize)]
125#[serde(rename_all = "camelCase")]
126struct AddTruthParams {
127 entity_name: String,
128 key: String,
129 value: String,
130}
131
132#[derive(Debug, Deserialize)]
133#[serde(rename_all = "camelCase")]
134struct DeleteTruthParams {
135 entity_name: String,
136 key: String,
137}
138
139pub async fn run_server(conn: Connection) -> Result<()> {
142 use std::io::{self, BufRead};
143 let stdin = io::stdin();
144 let mut reader = stdin.lock();
145 let mut line = String::new();
146
147 while reader.read_line(&mut line)? > 0 {
148 let trimmed = line.trim();
149 if trimmed.is_empty() {
150 line.clear();
151 continue;
152 }
153
154 let req: JsonRpcRequest = match serde_json::from_str(trimmed) {
155 Ok(r) => r,
156 Err(_) => {
157 line.clear();
158 continue;
159 }
160 };
161
162 let is_notification = req.id.is_null();
164 let req_id = req.id.clone();
165
166 let response = match dispatch(&conn, req).await {
167 Ok(res) => res,
168 Err(e) => JsonRpcResponse {
169 jsonrpc: "2.0".to_string(),
170 id: req_id,
171 result: None,
172 error: Some(serde_json::json!({
173 "code": -32603,
174 "message": e.to_string()
175 })),
176 },
177 };
178
179 if !is_notification {
180 println!("{}", serde_json::to_string(&response)?);
181 }
182 line.clear();
183 }
184 Ok(())
185}
186
187async fn dispatch(conn: &Connection, req: JsonRpcRequest) -> Result<JsonRpcResponse> {
188 match req.method.as_str() {
189 "initialize" => Ok(handle_initialize(req.id)),
190 "notifications/initialized" => Ok(noop(req.id)),
191 "tools/list" => Ok(handle_tools_list(req.id)),
192 "tools/call" => handle_tools_call(conn, req.id, req.params.unwrap_or_default()).await,
193 _ => Ok(method_not_found(req.id, &req.method)),
194 }
195}
196
197fn handle_initialize(id: serde_json::Value) -> JsonRpcResponse {
200 JsonRpcResponse {
201 jsonrpc: "2.0".to_string(),
202 id,
203 result: Some(serde_json::json!({
204 "protocolVersion": "2024-11-05",
205 "capabilities": { "tools": {} },
206 "serverInfo": {
207 "name": "miku",
208 "version": env!("CARGO_PKG_VERSION")
209 }
210 })),
211 error: None,
212 }
213}
214
215fn handle_tools_list(id: serde_json::Value) -> JsonRpcResponse {
216 JsonRpcResponse {
217 jsonrpc: "2.0".to_string(),
218 id,
219 result: Some(serde_json::json!({
220 "tools": [
221 {
222 "name": "create_entities",
223 "description": "Create multiple new entities in the knowledge graph",
224 "inputSchema": {
225 "type": "object",
226 "properties": {
227 "entities": {
228 "type": "array",
229 "items": {
230 "type": "object",
231 "properties": {
232 "name": {"type": "string"},
233 "entityType": {"type": "string"},
234 "observations": {"type": "array", "items": {"type": "string"}}
235 },
236 "required": ["name", "entityType", "observations"]
237 }
238 }
239 },
240 "required": ["entities"]
241 }
242 },
243 {
244 "name": "create_relations",
245 "description": "Create relations between entities in the knowledge graph",
246 "inputSchema": {
247 "type": "object",
248 "properties": {
249 "relations": {
250 "type": "array",
251 "items": {
252 "type": "object",
253 "properties": {
254 "from": {"type": "string"},
255 "to": {"type": "string"},
256 "relationType": {"type": "string"}
257 },
258 "required": ["from", "to", "relationType"]
259 }
260 }
261 },
262 "required": ["relations"]
263 }
264 },
265 {
266 "name": "add_observations",
267 "description": "Add new observations to existing entities (subject to the observation limit cap, which defaults to 50)",
268 "inputSchema": {
269 "type": "object",
270 "properties": {
271 "observations": {
272 "type": "array",
273 "items": {
274 "type": "object",
275 "properties": {
276 "entityName": {"type": "string"},
277 "contents": {"type": "array", "items": {"type": "string"}}
278 },
279 "required": ["entityName", "contents"]
280 }
281 }
282 },
283 "required": ["observations"]
284 }
285 },
286 {
287 "name": "add_truth",
288 "description": "Add or update a truth key-value pair for an entity",
289 "inputSchema": {
290 "type": "object",
291 "properties": {
292 "entityName": {"type": "string"},
293 "key": {"type": "string"},
294 "value": {"type": "string"}
295 },
296 "required": ["entityName", "key", "value"]
297 }
298 },
299 {
300 "name": "delete_truth",
301 "description": "Delete a specific truth key by name from an entity",
302 "inputSchema": {
303 "type": "object",
304 "properties": {
305 "entityName": {"type": "string"},
306 "key": {"type": "string"}
307 },
308 "required": ["entityName", "key"]
309 }
310 },
311 {
312 "name": "delete_entities",
313 "description": "Delete entities and their associated relations",
314 "inputSchema": {
315 "type": "object",
316 "properties": {
317 "entityNames": {"type": "array", "items": {"type": "string"}}
318 },
319 "required": ["entityNames"]
320 }
321 },
322 {
323 "name": "delete_observations",
324 "description": "Delete specific observations from entities",
325 "inputSchema": {
326 "type": "object",
327 "properties": {
328 "deletions": {
329 "type": "array",
330 "items": {
331 "type": "object",
332 "properties": {
333 "entityName": {"type": "string"},
334 "observations": {"type": "array", "items": {"type": "string"}}
335 },
336 "required": ["entityName", "observations"]
337 }
338 }
339 },
340 "required": ["deletions"]
341 }
342 },
343 {
344 "name": "delete_relations",
345 "description": "Delete specific relations from the knowledge graph",
346 "inputSchema": {
347 "type": "object",
348 "properties": {
349 "relations": {
350 "type": "array",
351 "items": {
352 "type": "object",
353 "properties": {
354 "from": {"type": "string"},
355 "to": {"type": "string"},
356 "relationType": {"type": "string"}
357 },
358 "required": ["from", "to", "relationType"]
359 }
360 }
361 },
362 "required": ["relations"]
363 }
364 },
365 {
366 "name": "read_graph",
367 "description": "Read the entire knowledge graph",
368 "inputSchema": {"type": "object", "properties": {}}
369 },
370 {
371 "name": "search_nodes",
372 "description": "Search for nodes in the knowledge graph by name, type, or observation content",
373 "inputSchema": {
374 "type": "object",
375 "properties": {
376 "query": {"type": "string"},
377 "limit": {
378 "type": "integer",
379 "minimum": 1,
380 "description": "Maximum number of matched nodes to return"
381 }
382 },
383 "required": ["query"]
384 }
385 },
386 {
387 "name": "open_nodes",
388 "description": "Retrieve specific nodes and their relations by name",
389 "inputSchema": {
390 "type": "object",
391 "properties": {
392 "names": {"type": "array", "items": {"type": "string"}}
393 },
394 "required": ["names"]
395 }
396 }
397 ]
398 })),
399 error: None,
400 }
401}
402
403pub async fn handle_tools_call(
406 conn: &Connection,
407 id: serde_json::Value,
408 params: serde_json::Value,
409) -> Result<JsonRpcResponse> {
410 let name = params["name"]
411 .as_str()
412 .ok_or_else(|| anyhow::anyhow!("tools/call missing 'name'"))?;
413 let args = params.get("arguments").cloned().unwrap_or_default();
414
415 let text = match name {
416 "create_entities" => {
417 let p: CreateEntitiesParams = serde_json::from_value(args)?;
418 let mut entities = p.entities;
419 let names: Vec<String> = entities
420 .iter()
421 .map(|e| crate::normalize::normalize_key(&e.name))
422 .collect();
423 for ent in &mut entities {
424 ent.name = crate::normalize::normalize_key(&ent.name);
425 }
426 db::mcp_create_entities(conn, entities).await?;
427 serde_json::to_string(&db::mcp_open_nodes(conn, names).await?)?
428 }
429 "create_relations" => {
430 let p: CreateRelationsParams = serde_json::from_value(args)?;
431 db::mcp_create_relations(conn, p.relations).await?;
432 "Relations created.".to_string()
433 }
434 "add_observations" => {
435 let mut p: AddObservationsParams = serde_json::from_value(args)?;
436 let mut obs_names: Vec<String> = Vec::new();
437 for obs in &mut p.observations {
438 let normalized = crate::normalize::normalize_key(&obs.entity_name);
439 obs.entity_name = normalized.clone();
440 obs_names.push(normalized);
441 }
442 let paths = crate::paths::MikuPaths::resolve();
443 let limit = std::env::var(crate::constant::ENV_OBSERVATION_LIMIT)
444 .ok()
445 .and_then(|v| v.parse::<usize>().ok())
446 .unwrap_or(paths.observation_limit.unwrap_or(50));
447 db::mcp_add_observations(conn, p.observations, limit).await?;
448 serde_json::to_string(&db::mcp_open_nodes(conn, obs_names).await?)?
449 }
450 "add_truth" => {
451 let p: AddTruthParams = serde_json::from_value(args)?;
452 let entity_name = crate::normalize::normalize_key(&p.entity_name);
453 db::truth_upsert(conn, &entity_name, &p.key, &p.value).await?;
454 serde_json::to_string(&db::mcp_open_nodes(conn, vec![entity_name]).await?)?
455 }
456 "delete_truth" => {
457 let p: DeleteTruthParams = serde_json::from_value(args)?;
458 let entity_name = crate::normalize::normalize_key(&p.entity_name);
459 db::truth_delete(conn, &entity_name, &p.key).await?;
460 serde_json::to_string(&db::mcp_open_nodes(conn, vec![entity_name]).await?)?
461 }
462 "delete_entities" => {
463 let p: DeleteEntitiesParams = serde_json::from_value(args)?;
464 let entity_names = p
465 .entity_names
466 .into_iter()
467 .map(|n| crate::normalize::normalize_key(&n))
468 .collect();
469 db::mcp_delete_entities(conn, entity_names).await?;
470 "Entities deleted.".to_string()
471 }
472 "delete_observations" => {
473 let p: DeleteObservationsParams = serde_json::from_value(args)?;
474 let mut deletions = p.deletions;
475 for del in &mut deletions {
476 del.entity_name = crate::normalize::normalize_key(&del.entity_name);
477 }
478 db::mcp_delete_observations(conn, deletions).await?;
479 "Observations deleted.".to_string()
480 }
481 "delete_relations" => {
482 let p: DeleteRelationsParams = serde_json::from_value(args)?;
483 let mut relations = p.relations;
484 for rel in &mut relations {
485 rel.from = crate::normalize::normalize_key(&rel.from);
486 rel.to = crate::normalize::normalize_key(&rel.to);
487 }
488 db::mcp_delete_relations(conn, relations).await?;
489 "Relations deleted.".to_string()
490 }
491 "read_graph" => {
492 let graph = db::mcp_read_graph(conn).await?;
493 serde_json::to_string(&graph)?
494 }
495 "search_nodes" => {
496 let p: SearchNodesParams = serde_json::from_value(args)?;
497 let graph = match p.limit {
498 Some(limit) => db::mcp_search_nodes_with_limit(conn, &p.query, limit).await?,
499 None => db::mcp_search_nodes(conn, &p.query).await?,
500 };
501 serde_json::to_string(&graph)?
502 }
503 "open_nodes" => {
504 let p: OpenNodesParams = serde_json::from_value(args)?;
505 let graph = db::mcp_open_nodes(conn, p.names).await?;
506 serde_json::to_string(&graph)?
507 }
508 unknown => {
509 return Ok(JsonRpcResponse {
510 jsonrpc: "2.0".to_string(),
511 id,
512 result: None,
513 error: Some(serde_json::json!({
514 "code": -32601,
515 "message": format!("Unknown tool: {}", unknown)
516 })),
517 });
518 }
519 };
520
521 Ok(JsonRpcResponse {
522 jsonrpc: "2.0".to_string(),
523 id,
524 result: Some(serde_json::json!({
525 "content": [{"type": "text", "text": text}],
526 "isError": false
527 })),
528 error: None,
529 })
530}
531
532fn noop(id: serde_json::Value) -> JsonRpcResponse {
535 JsonRpcResponse {
536 jsonrpc: "2.0".to_string(),
537 id,
538 result: Some(serde_json::Value::Null),
539 error: None,
540 }
541}
542
543fn method_not_found(id: serde_json::Value, method: &str) -> JsonRpcResponse {
544 JsonRpcResponse {
545 jsonrpc: "2.0".to_string(),
546 id,
547 result: None,
548 error: Some(serde_json::json!({
549 "code": -32601,
550 "message": format!("Method not found: {}", method)
551 })),
552 }
553}