ltcai 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server.py CHANGED
@@ -48,6 +48,13 @@ from llm_router import AsyncOpenAI, LLMRouter, OPENAI_COMPATIBLE_PROVIDERS, HF_M
48
48
  from knowledge_graph import KnowledgeGraphStore
49
49
  from p_reinforce import BRAIN_DIR, PReinforceGardener
50
50
  from setup import get_recommendations, install_stream, open_url, scan_environment
51
+ from auto_setup import (
52
+ plan as auto_setup_plan,
53
+ preset as auto_setup_preset,
54
+ probe as auto_setup_probe,
55
+ recommend as auto_setup_recommend,
56
+ verify as auto_setup_verify,
57
+ )
51
58
  from telegram_bot import broadcast_web_chat
52
59
  from tools import (
53
60
  AGENT_ROOT,
@@ -387,6 +394,14 @@ class McpRecommendRequest(BaseModel):
387
394
  class McpInstallRequest(BaseModel):
388
395
  mcp_id: str
389
396
 
397
+ class McpCustomRequest(BaseModel):
398
+ name: str
399
+ package: str
400
+ description: str = ""
401
+ category: str = "custom"
402
+ icon: str = "🔌"
403
+ env_vars: List[Dict] = []
404
+
390
405
  class SkillInstallRequest(BaseModel):
391
406
  plugin: str
392
407
  skill: str
@@ -673,6 +688,187 @@ MCP_REGISTRY = [
673
688
  "keywords": ["canva", "design", "poster", "card", "캔바", "디자인"],
674
689
  "capabilities": ["디자인 템플릿", "이미지 제작 워크플로"],
675
690
  },
691
+ # ── 데이터베이스 ─────────────────────────────────────────────────────────
692
+ {
693
+ "id": "mcp-postgres",
694
+ "name": "PostgreSQL MCP",
695
+ "category": "Database",
696
+ "install_mode": "npm",
697
+ "package": "@modelcontextprotocol/server-postgres",
698
+ "description": "PostgreSQL 데이터베이스에 연결해 쿼리 실행, 스키마 탐색, 데이터 분석을 수행합니다.",
699
+ "keywords": ["postgres", "postgresql", "database", "sql", "db", "데이터베이스", "쿼리"],
700
+ "capabilities": ["SQL 쿼리 실행", "스키마 탐색", "테이블 분석"],
701
+ "env_vars": [{"name": "POSTGRES_CONNECTION_STRING", "description": "postgresql://user:pass@host:5432/db"}],
702
+ },
703
+ {
704
+ "id": "mcp-sqlite",
705
+ "name": "SQLite MCP",
706
+ "category": "Database",
707
+ "install_mode": "npm",
708
+ "package": "@modelcontextprotocol/server-sqlite",
709
+ "description": "로컬 SQLite 파일에 쿼리를 실행하고 데이터를 탐색합니다.",
710
+ "keywords": ["sqlite", "database", "sql", "local", "로컬", "데이터베이스"],
711
+ "capabilities": ["SQLite 쿼리", "테이블 탐색", "데이터 집계"],
712
+ "env_vars": [{"name": "SQLITE_DB_PATH", "description": "/path/to/database.db"}],
713
+ },
714
+ # ── 검색 / 웹 ────────────────────────────────────────────────────────────
715
+ {
716
+ "id": "mcp-brave-search",
717
+ "name": "Brave Search MCP",
718
+ "category": "Search / web",
719
+ "install_mode": "npm",
720
+ "package": "@modelcontextprotocol/server-brave-search",
721
+ "description": "Brave Search API로 실시간 웹 검색 결과를 가져옵니다.",
722
+ "keywords": ["search", "web", "brave", "websearch", "검색", "웹검색"],
723
+ "capabilities": ["실시간 웹 검색", "뉴스 검색", "이미지 검색"],
724
+ "env_vars": [{"name": "BRAVE_API_KEY", "description": "Brave Search API 키 (search.brave.com)"}],
725
+ },
726
+ {
727
+ "id": "mcp-tavily",
728
+ "name": "Tavily Search MCP",
729
+ "category": "Search / web",
730
+ "install_mode": "npm",
731
+ "package": "tavily-mcp",
732
+ "description": "AI 최적화 웹 검색 엔진 Tavily로 고품질 검색 결과를 가져옵니다.",
733
+ "keywords": ["search", "tavily", "ai search", "검색", "AI검색"],
734
+ "capabilities": ["AI 최적화 검색", "요약 검색 결과"],
735
+ "env_vars": [{"name": "TAVILY_API_KEY", "description": "app.tavily.com에서 발급"}],
736
+ },
737
+ {
738
+ "id": "mcp-puppeteer",
739
+ "name": "Puppeteer MCP",
740
+ "category": "Browser automation",
741
+ "install_mode": "npm",
742
+ "package": "@modelcontextprotocol/server-puppeteer",
743
+ "description": "Puppeteer로 브라우저를 제어하고 웹 스크래핑, 스크린샷, 자동화를 수행합니다.",
744
+ "keywords": ["puppeteer", "browser", "scraping", "screenshot", "automation", "스크래핑", "자동화"],
745
+ "capabilities": ["웹 스크래핑", "스크린샷", "폼 자동화", "클릭/입력"],
746
+ },
747
+ # ── 배포 / 인프라 ─────────────────────────────────────────────────────────
748
+ {
749
+ "id": "mcp-vercel",
750
+ "name": "Vercel MCP",
751
+ "category": "Deployment",
752
+ "install_mode": "npm",
753
+ "package": "@vercel/mcp-adapter",
754
+ "description": "Vercel 프로젝트 배포 상태 확인, 로그 조회, 환경 변수 관리를 수행합니다.",
755
+ "keywords": ["vercel", "deploy", "deployment", "serverless", "배포", "버셀"],
756
+ "capabilities": ["배포 상태 확인", "로그 조회", "환경 변수 관리"],
757
+ "env_vars": [{"name": "VERCEL_API_TOKEN", "description": "Vercel 계정 토큰"}],
758
+ },
759
+ {
760
+ "id": "mcp-cloudflare",
761
+ "name": "Cloudflare MCP",
762
+ "category": "Deployment / CDN",
763
+ "install_mode": "npm",
764
+ "package": "@cloudflare/mcp-server-cloudflare",
765
+ "description": "Cloudflare Workers, KV, R2, D1 등 Cloudflare 서비스를 관리합니다.",
766
+ "keywords": ["cloudflare", "workers", "cdn", "kv", "r2", "클라우드플레어"],
767
+ "capabilities": ["Workers 배포", "KV/R2 관리", "DNS 조회", "D1 쿼리"],
768
+ "env_vars": [{"name": "CLOUDFLARE_API_TOKEN", "description": "Cloudflare API 토큰"}],
769
+ },
770
+ {
771
+ "id": "mcp-docker",
772
+ "name": "Docker MCP",
773
+ "category": "Infrastructure",
774
+ "install_mode": "npm",
775
+ "package": "docker-mcp",
776
+ "description": "Docker 컨테이너 목록 조회, 실행/중지, 로그 확인을 수행합니다.",
777
+ "keywords": ["docker", "container", "devops", "도커", "컨테이너", "인프라"],
778
+ "capabilities": ["컨테이너 관리", "이미지 조회", "로그 확인", "실행/중지"],
779
+ },
780
+ # ── SaaS / 결제 ───────────────────────────────────────────────────────────
781
+ {
782
+ "id": "mcp-stripe",
783
+ "name": "Stripe MCP",
784
+ "category": "Payments",
785
+ "install_mode": "npm",
786
+ "package": "@stripe/agent-toolkit",
787
+ "description": "Stripe 결제, 고객, 구독, 인보이스를 조회하고 관리합니다.",
788
+ "keywords": ["stripe", "payment", "billing", "subscription", "결제", "스트라이프"],
789
+ "capabilities": ["결제 조회", "고객 관리", "구독 확인", "인보이스"],
790
+ "env_vars": [{"name": "STRIPE_SECRET_KEY", "description": "Stripe Secret Key (sk_...)"}],
791
+ },
792
+ {
793
+ "id": "mcp-supabase",
794
+ "name": "Supabase MCP",
795
+ "category": "Database / BaaS",
796
+ "install_mode": "npm",
797
+ "package": "@supabase/mcp-server-supabase",
798
+ "description": "Supabase 프로젝트의 DB 쿼리, Auth 관리, Storage 파일 접근을 수행합니다.",
799
+ "keywords": ["supabase", "database", "auth", "storage", "supabase", "슈퍼베이스"],
800
+ "capabilities": ["DB 쿼리", "Auth 사용자 조회", "Storage 파일 관리"],
801
+ "env_vars": [
802
+ {"name": "SUPABASE_URL", "description": "https://xxx.supabase.co"},
803
+ {"name": "SUPABASE_SERVICE_ROLE_KEY", "description": "service_role 키"},
804
+ ],
805
+ },
806
+ {
807
+ "id": "mcp-hubspot",
808
+ "name": "HubSpot MCP",
809
+ "category": "CRM / marketing",
810
+ "install_mode": "npm",
811
+ "package": "@hubspot/mcp-server",
812
+ "description": "HubSpot CRM의 연락처, 딜, 캠페인 데이터를 조회하고 분석합니다.",
813
+ "keywords": ["hubspot", "crm", "marketing", "sales", "허브스팟", "CRM"],
814
+ "capabilities": ["연락처 조회", "딜 파이프라인", "캠페인 분석"],
815
+ "env_vars": [{"name": "HUBSPOT_ACCESS_TOKEN", "description": "HubSpot Private App 토큰"}],
816
+ },
817
+ # ── AI / 메모리 ───────────────────────────────────────────────────────────
818
+ {
819
+ "id": "mcp-memory",
820
+ "name": "Memory MCP (공식)",
821
+ "category": "Memory / knowledge",
822
+ "install_mode": "npm",
823
+ "package": "@modelcontextprotocol/server-memory",
824
+ "description": "대화 간 지속 메모리를 저장하고 검색하는 공식 MCP 서버입니다.",
825
+ "keywords": ["memory", "remember", "knowledge", "기억", "메모리", "지식"],
826
+ "capabilities": ["장기 기억 저장", "메모리 검색", "엔티티 추적"],
827
+ },
828
+ {
829
+ "id": "mcp-sequential-thinking",
830
+ "name": "Sequential Thinking MCP",
831
+ "category": "AI / reasoning",
832
+ "install_mode": "npm",
833
+ "package": "@modelcontextprotocol/server-sequential-thinking",
834
+ "description": "복잡한 문제를 단계별로 분해해 추론하는 사고 흐름 도구입니다.",
835
+ "keywords": ["reasoning", "thinking", "chain of thought", "추론", "사고"],
836
+ "capabilities": ["단계별 추론", "문제 분해", "사고 흐름 추적"],
837
+ },
838
+ # ── 커뮤니케이션 ──────────────────────────────────────────────────────────
839
+ {
840
+ "id": "mcp-discord",
841
+ "name": "Discord MCP",
842
+ "category": "Communication",
843
+ "install_mode": "npm",
844
+ "package": "discord-mcp",
845
+ "description": "Discord 서버 채널 메시지 전송, 읽기, 관리 자동화를 수행합니다.",
846
+ "keywords": ["discord", "message", "channel", "디스코드", "메시지", "알림"],
847
+ "capabilities": ["메시지 전송", "채널 읽기", "알림 자동화"],
848
+ "env_vars": [{"name": "DISCORD_BOT_TOKEN", "description": "Discord Bot 토큰"}],
849
+ },
850
+ {
851
+ "id": "mcp-telegram",
852
+ "name": "Telegram MCP",
853
+ "category": "Communication",
854
+ "install_mode": "npm",
855
+ "package": "telegram-mcp",
856
+ "description": "Telegram 봇을 통한 메시지 전송, 수신, 알림 자동화를 수행합니다.",
857
+ "keywords": ["telegram", "bot", "message", "텔레그램", "봇", "메시지"],
858
+ "capabilities": ["메시지 전송/수신", "알림 자동화", "그룹 관리"],
859
+ "env_vars": [{"name": "TELEGRAM_BOT_TOKEN", "description": "BotFather에서 발급한 토큰"}],
860
+ },
861
+ # ── 개발 도구 ─────────────────────────────────────────────────────────────
862
+ {
863
+ "id": "mcp-everything",
864
+ "name": "Everything MCP (테스트)",
865
+ "category": "Developer tools",
866
+ "install_mode": "npm",
867
+ "package": "@modelcontextprotocol/server-everything",
868
+ "description": "MCP 연결 테스트용 모든 기능이 포함된 데모 서버입니다.",
869
+ "keywords": ["test", "demo", "everything", "테스트", "개발"],
870
+ "capabilities": ["MCP 기능 테스트", "프로토타입"],
871
+ },
676
872
  ]
677
873
 
678
874
  # ── Remote MCP Registry (registry.modelcontextprotocol.io) ───────────────────
@@ -4145,6 +4341,17 @@ async def knowledge_graph_stats(request: Request):
4145
4341
  require_user(request)
4146
4342
  return KNOWLEDGE_GRAPH.stats()
4147
4343
 
4344
+ @app.get("/knowledge-graph/schema")
4345
+ async def knowledge_graph_schema(request: Request):
4346
+ _require_graph()
4347
+ require_user(request)
4348
+ stats = KNOWLEDGE_GRAPH.stats()
4349
+ return {
4350
+ "legacy_schema_version": stats.get("schema_version"),
4351
+ "v2_schema_available": stats.get("v2_schema_available"),
4352
+ "v2": stats.get("v2"),
4353
+ }
4354
+
4148
4355
 
4149
4356
  @app.get("/knowledge-graph/graph")
4150
4357
  async def knowledge_graph_data(request: Request, limit: int = 300):
@@ -6316,6 +6523,106 @@ async def mcp_registry_refresh(request: Request):
6316
6523
  return {"status": "ok", "total": len(registry), "remote": len(_REMOTE_REGISTRY_CACHE)}
6317
6524
 
6318
6525
 
6526
+ @app.get("/mcp/claude-code-servers")
6527
+ async def mcp_claude_code_servers(request: Request):
6528
+ """Read ~/.claude/settings.json mcpServers and return them as Lattice MCP items."""
6529
+ require_user(request)
6530
+ settings_path = Path.home() / ".claude" / "settings.json"
6531
+ if not settings_path.exists():
6532
+ return {"servers": []}
6533
+ try:
6534
+ with open(settings_path, "r", encoding="utf-8") as f:
6535
+ settings = json.load(f)
6536
+ mcp_servers = settings.get("mcpServers", {})
6537
+ servers = []
6538
+ for name, cfg in mcp_servers.items():
6539
+ cmd = cfg.get("command", "")
6540
+ args = cfg.get("args", [])
6541
+ package = " ".join([cmd] + args) if args else cmd
6542
+ env = cfg.get("env", {})
6543
+ env_vars = [{"name": k, "value": v} for k, v in env.items()]
6544
+ servers.append({
6545
+ "id": f"claude-code:{name}",
6546
+ "name": name,
6547
+ "description": f"Claude Code MCP: {package}",
6548
+ "package": package,
6549
+ "icon": "🤖",
6550
+ "category": "Claude Code",
6551
+ "source": "claude-code",
6552
+ "installed": True,
6553
+ "env_vars": env_vars,
6554
+ })
6555
+ return {"servers": servers}
6556
+ except Exception as e:
6557
+ logging.warning("mcp_claude_code_servers failed: %s", e)
6558
+ return {"servers": []}
6559
+
6560
+
6561
+ _CUSTOM_MCP_FILE = DATA_DIR / "custom_mcps.json"
6562
+
6563
+ def _load_custom_mcps() -> List[Dict]:
6564
+ if not _CUSTOM_MCP_FILE.exists():
6565
+ return []
6566
+ try:
6567
+ with open(_CUSTOM_MCP_FILE, "r", encoding="utf-8") as f:
6568
+ return json.load(f)
6569
+ except Exception:
6570
+ return []
6571
+
6572
+ def _save_custom_mcps(items: List[Dict]):
6573
+ with open(_CUSTOM_MCP_FILE, "w", encoding="utf-8") as f:
6574
+ json.dump(items, f, ensure_ascii=False, indent=2)
6575
+
6576
+
6577
+ @app.get("/mcp/custom")
6578
+ async def mcp_custom_list(request: Request):
6579
+ """Return user-added custom MCP entries."""
6580
+ require_user(request)
6581
+ return {"custom": _load_custom_mcps()}
6582
+
6583
+
6584
+ @app.post("/mcp/custom")
6585
+ async def mcp_custom_add(req: McpCustomRequest, request: Request):
6586
+ """Save a custom MCP entry (user-defined)."""
6587
+ require_user(request)
6588
+ if not req.name.strip():
6589
+ raise HTTPException(status_code=400, detail="name은 필수입니다.")
6590
+ if not req.package.strip():
6591
+ raise HTTPException(status_code=400, detail="package는 필수입니다.")
6592
+ items = _load_custom_mcps()
6593
+ entry = {
6594
+ "id": f"custom:{req.name.strip().lower().replace(' ', '-')}",
6595
+ "name": req.name.strip(),
6596
+ "package": req.package.strip(),
6597
+ "description": req.description.strip(),
6598
+ "category": req.category or "custom",
6599
+ "icon": req.icon or "🔌",
6600
+ "env_vars": req.env_vars or [],
6601
+ "install_mode": "npm",
6602
+ "source": "custom",
6603
+ "installed": False,
6604
+ "added_at": datetime.now().isoformat(),
6605
+ }
6606
+ # overwrite if same id
6607
+ items = [e for e in items if e["id"] != entry["id"]]
6608
+ items.append(entry)
6609
+ _save_custom_mcps(items)
6610
+ return {"status": "ok", "entry": entry}
6611
+
6612
+
6613
+ @app.delete("/mcp/custom/{mcp_id:path}")
6614
+ async def mcp_custom_delete(mcp_id: str, request: Request):
6615
+ """Remove a custom MCP entry."""
6616
+ require_admin(request)
6617
+ items = _load_custom_mcps()
6618
+ before = len(items)
6619
+ items = [e for e in items if e["id"] != mcp_id]
6620
+ if len(items) == before:
6621
+ raise HTTPException(status_code=404, detail="항목을 찾을 수 없습니다.")
6622
+ _save_custom_mcps(items)
6623
+ return {"status": "ok"}
6624
+
6625
+
6319
6626
  # ── Skills & Plugin Directory endpoints ───────────────────────────────────────
6320
6627
 
6321
6628
  @app.get("/skills/marketplace")
@@ -6483,13 +6790,37 @@ async def garden_tree(request: Request):
6483
6790
  class SetupInstallRequest(BaseModel):
6484
6791
  items: List[Dict]
6485
6792
 
6793
+ def setup_auto_state() -> Dict[str, object]:
6794
+ """Return the PPT-aligned zero-config setup state used by setup UI/API."""
6795
+ profile = auto_setup_probe()
6796
+ recommendation = auto_setup_recommend(profile)
6797
+ install_plan = auto_setup_plan(profile, recommendation)
6798
+ return {
6799
+ "probe": profile.to_json(),
6800
+ "recommend": recommendation.to_json(),
6801
+ "plan": install_plan.to_json(),
6802
+ "verify": auto_setup_verify(profile, recommendation),
6803
+ "preset": auto_setup_preset(profile, recommendation),
6804
+ }
6805
+
6486
6806
  @app.get("/setup/scan")
6487
6807
  async def setup_scan(request: Request):
6488
6808
  """환경 감지 및 맞춤 추천 반환."""
6489
6809
  require_user(request)
6490
6810
  env = scan_environment()
6491
6811
  recs = get_recommendations(env)
6492
- return {"environment": env, "recommendations": recs}
6812
+ zero_config = setup_auto_state()
6813
+ env["zero_config"] = zero_config
6814
+ recs.setdefault("summary", {})["zero_config"] = zero_config["recommend"]
6815
+ recs["install_plan"] = zero_config["plan"]
6816
+ recs["preset"] = zero_config["preset"]
6817
+ return {"environment": env, "recommendations": recs, "zero_config": zero_config}
6818
+
6819
+ @app.get("/setup/auto")
6820
+ async def setup_auto(request: Request):
6821
+ """PPT-aligned zero-config setup pipeline: probe → recommend → plan → verify → preset."""
6822
+ require_user(request)
6823
+ return setup_auto_state()
6493
6824
 
6494
6825
  @app.post("/setup/install")
6495
6826
  async def setup_install(req: SetupInstallRequest, request: Request):