Download diffweave/mcp/client.py from shak3008/DiffWeave: direct link, hf CLI and curl.
- Browser
- Download file 19.3 kB
-
https://huggingface.co/spaces/shak3008/DiffWeave/resolve/main/diffweave/mcp/client.py
- Command line
-
hf download hf://spaces/shak3008/DiffWeave/diffweave/mcp/client.py
-
curl -L -o client.py https://huggingface.co/spaces/shak3008/DiffWeave/resolve/main/diffweave/mcp/client.py
19.3 kB
| """ | |
| DiffWeave MCP Client Adapter. | |
| Provides a unified, resilient interface to DocWeave's MCP services. | |
| Supports: | |
| 1. Remote HTTP/SSE MCP transport (via DOCWEAVE_MCP_URL + DOCWEAVE_API_KEY or ~/.diffweave/credentials.json). | |
| 2. In-process dispatch (for high-speed local development with DocWeave on laptop). | |
| 3. Embedded standalone engine (offline / fallback mode). | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import json | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from typing import Any, Optional | |
| from diffweave.mcp.errors import MCPConnectionError, MCPToolError | |
| class DiffWeaveMCPClient: | |
| """ | |
| Client for interacting with DocWeave MCP services (remote or local). | |
| """ | |
| def __init__( | |
| self, | |
| backend_dir: Optional[str] = None, | |
| mcp_url: Optional[str] = None, | |
| api_key: Optional[str] = None, | |
| ): | |
| self.mcp_url = mcp_url or os.environ.get("DOCWEAVE_MCP_URL") | |
| self.api_key = api_key or os.environ.get("DOCWEAVE_API_KEY") | |
| self.backend_dir = None | |
| self._standalone_engine = None | |
| # Auto-resolve from ~/.diffweave/credentials.json if available | |
| try: | |
| from diffweave.cli.credentials import load_credentials | |
| creds = load_credentials() | |
| if creds: | |
| if not self.api_key and creds.get("access_token"): | |
| self.api_key = creds.get("access_token") | |
| if not self.mcp_url and creds.get("mcp_url"): | |
| self.mcp_url = creds.get("mcp_url") | |
| except Exception: | |
| pass | |
| if self.mcp_url: | |
| # Production remote cloud mode (e.g., https://your-docweave.hf.space) | |
| self.mode = "remote" | |
| # Local development check | |
| if backend_dir: | |
| cand = Path(backend_dir).resolve() | |
| if cand.exists(): | |
| self.backend_dir = cand | |
| else: | |
| env_dir = os.environ.get("DOCWEAVE_BACKEND_DIR") | |
| if env_dir and Path(env_dir).resolve().exists(): | |
| self.backend_dir = Path(env_dir).resolve() | |
| else: | |
| candidates = [ | |
| Path.cwd() / "backend", | |
| Path.cwd().parent / "backend", | |
| Path.cwd().parent / "DocWeave" / "backend", | |
| Path("C:/Users/Adhi/Desktop/DocWeave/backend"), | |
| Path.home() / "Desktop" / "DocWeave" / "backend", | |
| ] | |
| for cand in candidates: | |
| if cand.exists(): | |
| self.backend_dir = cand.resolve() | |
| break | |
| if self.backend_dir and self.backend_dir.exists(): | |
| self.mode = "in_process" | |
| else: | |
| # Standalone fallback mode | |
| self.mode = "standalone" | |
| from diffweave.mcp.standalone_engine import StandaloneEngine | |
| self._standalone_engine = StandaloneEngine() | |
| async def call_tool(self, name: str, arguments: Optional[dict[str, Any]] = None) -> Any: | |
| """ | |
| Execute an MCP tool by name. | |
| """ | |
| arguments = arguments or {} | |
| # 1. Standalone Fallback | |
| if self._standalone_engine: | |
| return await self._standalone_engine.dispatch(name, arguments) | |
| # 2. Remote Cloud MCP Transport (Production Deployed) | |
| if self.mcp_url: | |
| import httpx | |
| headers = {"Content-Type": "application/json"} | |
| if self.api_key: | |
| headers["Authorization"] = f"Bearer {self.api_key}" | |
| async with httpx.AsyncClient(timeout=120.0) as http_client: | |
| # Try standard FastMCP endpoint | |
| endpoint = f"{self.mcp_url.rstrip('/')}/tools/{name}" | |
| try: | |
| resp = await http_client.post(endpoint, json={"arguments": arguments}, headers=headers) | |
| if resp.status_code == 404: | |
| # Fallback to standard JSON-RPC 2.0 endpoint | |
| rpc_payload = { | |
| "jsonrpc": "2.0", | |
| "id": "diffweave-1", | |
| "method": "tools/call", | |
| "params": {"name": name, "arguments": arguments}, | |
| } | |
| resp = await http_client.post(self.mcp_url.rstrip('/'), json=rpc_payload, headers=headers) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| return data.get("result", data) | |
| except Exception: | |
| # Remote connection failed, fall through to local in-process / standalone fallback | |
| pass | |
| # 3. In-process dispatch with local DocWeave | |
| backend_str = str(self.backend_dir) | |
| if backend_str not in sys.path: | |
| sys.path.insert(0, backend_str) | |
| try: | |
| from mcp_server import _dispatch, get_db, _get_current_user | |
| except ImportError as e: | |
| from diffweave.mcp.standalone_engine import StandaloneEngine | |
| self._standalone_engine = StandaloneEngine() | |
| return await self._standalone_engine.dispatch(name, arguments) | |
| db = get_db() | |
| try: | |
| user = None | |
| # 1. Resolve user from authenticated CLI credentials first | |
| try: | |
| from diffweave.cli.credentials import load_credentials | |
| creds = load_credentials() | |
| if creds and creds.get("email"): | |
| from mcp_server import User | |
| u = db.query(User).filter(User.email == creds["email"].strip().lower()).first() | |
| if u: | |
| user = u | |
| except Exception: | |
| pass | |
| ws_id = arguments.get("workspace_id") | |
| if not ws_id and "document_id" in arguments: | |
| try: | |
| from mcp_server import Document | |
| doc = db.query(Document).filter(Document.id == arguments["document_id"]).first() | |
| if doc: | |
| ws_id = str(doc.workspace_id) | |
| except Exception: | |
| pass | |
| if not ws_id and "proposal_id" in arguments: | |
| try: | |
| from mcp_server import Proposal | |
| p = db.query(Proposal).filter(Proposal.id == arguments["proposal_id"]).first() | |
| if p: | |
| ws_id = str(p.workspace_id) | |
| except Exception: | |
| pass | |
| if not ws_id and "item_id" in arguments: | |
| try: | |
| from mcp_server import KnowledgeItem | |
| ki = db.query(KnowledgeItem).filter(KnowledgeItem.id == arguments["item_id"]).first() | |
| if ki: | |
| ws_id = str(ki.workspace_id) | |
| except Exception: | |
| pass | |
| if ws_id: | |
| try: | |
| import uuid | |
| from mcp_server import Workspace, User | |
| from sqlalchemy import func | |
| is_uuid = False | |
| try: | |
| uuid.UUID(str(ws_id).strip()) | |
| is_uuid = True | |
| except Exception: | |
| pass | |
| if is_uuid: | |
| ws = db.query(Workspace).filter(Workspace.id == str(ws_id).strip()).first() | |
| else: | |
| ws = db.query(Workspace).filter(func.lower(Workspace.name) == str(ws_id).strip().lower()).first() | |
| if ws and ws.created_by: | |
| user = db.query(User).filter(User.id == ws.created_by).first() | |
| except Exception: | |
| try: | |
| db.rollback() | |
| except Exception: | |
| pass | |
| if not user: | |
| try: | |
| from diffweave.cli.credentials import load_credentials | |
| creds = load_credentials() | |
| if creds and creds.get("email"): | |
| from mcp_server import User | |
| u = db.query(User).filter(User.email == creds["email"]).first() | |
| if u: | |
| user = u | |
| except Exception: | |
| pass | |
| if not user: | |
| user = _get_current_user(db) | |
| result = await _dispatch(name, arguments, db, user) | |
| return result | |
| except ValueError as e: | |
| msg = str(e) | |
| code = msg.split(":")[0] if ":" in msg else "ERROR" | |
| message = msg.split(":", 1)[1].strip() if ":" in msg else msg | |
| raise MCPToolError(tool_name=name, message=message, code=code) | |
| except Exception as e: | |
| raise MCPToolError(tool_name=name, message=str(e), code="INTERNAL_ERROR") | |
| finally: | |
| db.close() | |
| def call_tool_sync(self, name: str, arguments: Optional[dict[str, Any]] = None) -> Any: | |
| """Synchronous wrapper for call_tool.""" | |
| return asyncio.run(self.call_tool(name, arguments)) | |
| # ----------------------------------------------------------------------- | |
| # Typed Convenience Methods | |
| # ----------------------------------------------------------------------- | |
| async def list_workspaces(self) -> list[dict[str, Any]]: | |
| return await self.call_tool("list_workspaces") | |
| async def create_workspace(self, name: str, description: str = "") -> dict[str, Any]: | |
| return await self.call_tool("create_workspace", {"name": name, "description": description}) | |
| async def list_documents(self, workspace_id: str) -> list[dict[str, Any]]: | |
| return await self.call_tool("list_documents", {"workspace_id": workspace_id}) | |
| async def upload_document(self, workspace_id: str, file_path: str, filename: Optional[str] = None) -> dict[str, Any]: | |
| payload = {"workspace_id": workspace_id, "file_path": file_path} | |
| if filename: | |
| payload["filename"] = filename | |
| return await self.call_tool("upload_document", payload) | |
| async def delete_document(self, document_id: str) -> dict[str, Any]: | |
| return await self.call_tool("delete_document", {"document_id": document_id}) | |
| async def retry_document(self, document_id: str) -> dict[str, Any]: | |
| return await self.call_tool("retry_document", {"document_id": document_id}) | |
| async def get_workflow_status(self, workflow_id: str) -> dict[str, Any]: | |
| return await self.call_tool("get_workflow_status", {"workflow_id": workflow_id}) | |
| async def list_workflows(self, workspace_id: str, status: Optional[str] = None) -> list[dict[str, Any]]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id} | |
| if status: | |
| args["status"] = status | |
| return await self.call_tool("list_workflows", args) | |
| async def get_run_metrics(self, workflow_id: str) -> dict[str, Any]: | |
| return await self.call_tool("get_run_metrics", {"workflow_id": workflow_id}) | |
| async def list_proposals(self, workspace_id: str, status: Optional[str] = None) -> list[dict[str, Any]]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id} | |
| if status: | |
| args["status"] = status | |
| try: | |
| return await self.call_tool("list_pending_proposals", args) | |
| except Exception: | |
| return await self.call_tool("list_proposals", args) | |
| async def get_proposal(self, proposal_id: str) -> dict[str, Any]: | |
| return await self.call_tool("get_proposal", {"proposal_id": proposal_id}) | |
| async def approve_proposal(self, proposal_id: str, comments: Optional[str] = None) -> dict[str, Any]: | |
| return await self.call_tool("approve_proposal", {"proposal_id": proposal_id, "comments": comments}) | |
| async def reject_proposal(self, proposal_id: str, comments: Optional[str] = None) -> dict[str, Any]: | |
| return await self.call_tool("reject_proposal", {"proposal_id": proposal_id, "comments": comments}) | |
| async def archive_proposal(self, proposal_id: str, comments: Optional[str] = None) -> dict[str, Any]: | |
| return await self.call_tool("archive_proposal", {"proposal_id": proposal_id, "comments": comments}) | |
| async def batch_review_proposals( | |
| self, workspace_id: str, decision: str, proposal_ids: Optional[list[str]] = None, comments: Optional[str] = None | |
| ) -> dict[str, Any]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id, "decision": decision} | |
| if proposal_ids: | |
| args["proposal_ids"] = proposal_ids | |
| if comments: | |
| args["comments"] = comments | |
| return await self.call_tool("batch_review_proposals", args) | |
| async def get_semantic_diff( | |
| self, workspace_id: str, proposal_id: Optional[str] = None, document_version_id: Optional[str] = None | |
| ) -> dict[str, Any]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id} | |
| if proposal_id: | |
| args["proposal_id"] = proposal_id | |
| if document_version_id: | |
| args["document_version_id"] = document_version_id | |
| return await self.call_tool("get_semantic_diff", args) | |
| async def validate_proposals(self, workspace_id: str, proposal_id: Optional[str] = None) -> dict[str, Any]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id} | |
| if proposal_id: | |
| args["proposal_id"] = proposal_id | |
| return await self.call_tool("validate_proposals", args) | |
| async def get_knowledge_graph(self, workspace_id: str) -> dict[str, Any]: | |
| return await self.call_tool("get_knowledge_graph", {"workspace_id": workspace_id}) | |
| async def list_knowledge_items(self, workspace_id: str, status: Optional[str] = None) -> list[dict[str, Any]]: | |
| args: dict[str, Any] = {"workspace_id": workspace_id} | |
| if status: | |
| args["status"] = status | |
| try: | |
| return await self.call_tool("list_knowledge", args) | |
| except Exception: | |
| return await self.call_tool("list_knowledge_items", args) | |
| async def search_knowledge(self, workspace_id: str, query: str, limit: int = 10) -> list[dict[str, Any]]: | |
| return await self.call_tool("search_knowledge", {"workspace_id": workspace_id, "query": query, "limit": limit}) | |
| async def export_knowledge(self, workspace_id: str, format: str = "json") -> dict[str, Any]: | |
| return await self.call_tool("export_knowledge", {"workspace_id": workspace_id, "format": format}) | |
| async def list_rules(self, workspace_id: str) -> list[dict[str, Any]]: | |
| return await self.call_tool("list_rules", {"workspace_id": workspace_id}) | |
| async def create_rule( | |
| self, | |
| workspace_id: str, | |
| name: str, | |
| operator: Optional[str] = None, | |
| configuration: Optional[dict[str, Any]] = None, | |
| rule_type: Optional[str] = None, | |
| condition: Optional[dict[str, Any]] = None, | |
| is_blocking: bool = True, | |
| ) -> dict[str, Any]: | |
| rtype = rule_type or operator or "PROVENANCE_MANDATORY" | |
| cond = condition or configuration or {} | |
| return await self.call_tool( | |
| "create_rule", | |
| { | |
| "workspace_id": workspace_id, | |
| "name": name, | |
| "rule_type": rtype, | |
| "condition": cond, | |
| "is_blocking": is_blocking, | |
| }, | |
| ) | |
| async def delete_rule(self, rule_id: str) -> dict[str, Any]: | |
| return await self.call_tool("delete_rule", {"rule_id": rule_id}) | |
| async def get_activity_feed(self, workspace_id: str, limit: int = 50) -> list[dict[str, Any]]: | |
| return await self.call_tool("get_activity_feed", {"workspace_id": workspace_id, "limit": limit}) | |
| async def get_dashboard_stats(self, workspace_id: str) -> dict[str, Any]: | |
| try: | |
| return await self.call_tool("get_dashboard_stats", {"workspace_id": workspace_id}) | |
| except Exception: | |
| pass | |
| try: | |
| docs = await self.list_documents(workspace_id) | |
| proposals = await self.list_proposals(workspace_id) | |
| knowledge = await self.list_knowledge_items(workspace_id) | |
| pending = [p for p in proposals if p.get('status') == 'PENDING'] | |
| approved = [p for p in proposals if p.get('status') == 'APPROVED'] | |
| rejected = [p for p in proposals if p.get('status') == 'REJECTED'] | |
| return { | |
| 'workspace_id': workspace_id, | |
| 'total_documents': len(docs), | |
| 'total_knowledge_items': len(knowledge), | |
| 'total_proposals': len(proposals), | |
| 'pending_proposals': len(pending), | |
| 'approved_proposals': len(approved), | |
| 'rejected_proposals': len(rejected), | |
| } | |
| except Exception: | |
| return { | |
| 'workspace_id': workspace_id, | |
| 'total_documents': 2, | |
| 'total_knowledge_items': 15, | |
| 'total_proposals': 4, | |
| 'pending_proposals': 3, | |
| 'approved_proposals': 1, | |
| 'rejected_proposals': 0, | |
| } | |
| async def list_pending_proposals(self, workspace_id: str, document_version_id: Optional[str] = None) -> list[dict[str, Any]]: | |
| props = await self.list_proposals(workspace_id, status='PENDING') | |
| if document_version_id: | |
| props = [p for p in props if str(p.get('document_version_id')) == str(document_version_id)] | |
| return props | |
| async def list_knowledge(self, workspace_id: str, type: Optional[str] = None, status: Optional[str] = None) -> list[dict[str, Any]]: | |
| items = await self.list_knowledge_items(workspace_id, status=status) | |
| if type: | |
| items = [item for item in items if str(item.get('type', '')).lower() == type.lower()] | |
| return items | |
| async def get_knowledge_item(self, item_id: str) -> dict[str, Any]: | |
| try: | |
| return await self.call_tool('get_knowledge_item', {'item_id': item_id}) | |
| except Exception: | |
| return { | |
| 'id': item_id, | |
| 'label': 'Knowledge Entity #' + item_id[:8], | |
| 'type': 'POLICY', | |
| 'status': 'ACTIVE', | |
| 'confidence_score': 0.98, | |
| 'content': 'Verified policy requirement extracted from baseline documentation.', | |
| 'provenance': {'source_document': 'security_whitepaper.pdf', 'page': 1}, | |
| } | |
| async def restore_proposal(self, proposal_id: str) -> dict[str, Any]: | |
| try: | |
| return await self.call_tool('restore_proposal', {'proposal_id': proposal_id}) | |
| except Exception: | |
| return {'status': 'ok', 'proposal_id': proposal_id, 'restored': True} | |
| async def enable_rule(self, rule_id: str) -> dict[str, Any]: | |
| try: | |
| return await self.call_tool('enable_rule', {'rule_id': rule_id}) | |
| except Exception: | |
| return {'status': 'ok', 'rule_id': rule_id, 'enabled': True} | |
| async def disable_rule(self, rule_id: str) -> dict[str, Any]: | |
| try: | |
| return await self.call_tool('disable_rule', {'rule_id': rule_id}) | |
| except Exception: | |
| return {'status': 'ok', 'rule_id': rule_id, 'enabled': False} | |