trishul-mcp 0.1.0__tar.gz

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.
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: trishul-mcp
3
+ Version: 0.1.0
4
+ Summary: Trishul: Deterministic repository-scale code intelligence Model Context Protocol (MCP) server.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: mcp>=1.0.0
8
+ Requires-Dist: httpx>=0.25.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+
11
+ # trishul-mcp
12
+
13
+ Deterministic, repository-scale semantic code intelligence Model Context Protocol (MCP) server for AI coding agents (Claude Code, Cursor, Windsurf).
14
+
15
+ ## Overview
16
+
17
+ `trishul-mcp` connects AI coding assistants directly to a persistent, topological code intelligence graph. Instead of burning your model's context window re-reading thousands of raw files or guessing caller hierarchies with naive regex, Trishul provides deterministic answers with verified certainty:
18
+
19
+ - **Callers (`get_callers`)**: Who directly or indirectly invokes a symbol?
20
+ - **Blast Radius (`get_blast` / `get_impact`)**: What breaks upstream if this function or interface changes?
21
+ - **Execution Paths (`get_path`)**: What is the optimal, deterministic call path between Symbol A and Symbol B?
22
+ - **Dependency Hubs (`get_dependency_hubs`)**: What are the critical architectural bottlenecks and chokepoints?
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install trishul-mcp
28
+ ```
29
+ *(Or install via `pip install llmgraph-in`)*
30
+
31
+ ## MCP Client Configuration
32
+
33
+ Add to your `claude_desktop_config.json`, Cursor MCP settings, or Windsurf configuration:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "trishul": {
39
+ "command": "trishul-mcp",
40
+ "env": {
41
+ "TRISHUL_API_KEY": "llmg_live_...",
42
+ "TRISHUL_API_URL": "https://api.llmgraph.in/v1"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ *Note: `LLMGRAPH_API_KEY` and `LLMGRAPH_API_URL` are also supported for backward compatibility.*
50
+
51
+ ## License
52
+
53
+ Apache-2.0. Developed by Brainfoods Studio.
@@ -0,0 +1,43 @@
1
+ # trishul-mcp
2
+
3
+ Deterministic, repository-scale semantic code intelligence Model Context Protocol (MCP) server for AI coding agents (Claude Code, Cursor, Windsurf).
4
+
5
+ ## Overview
6
+
7
+ `trishul-mcp` connects AI coding assistants directly to a persistent, topological code intelligence graph. Instead of burning your model's context window re-reading thousands of raw files or guessing caller hierarchies with naive regex, Trishul provides deterministic answers with verified certainty:
8
+
9
+ - **Callers (`get_callers`)**: Who directly or indirectly invokes a symbol?
10
+ - **Blast Radius (`get_blast` / `get_impact`)**: What breaks upstream if this function or interface changes?
11
+ - **Execution Paths (`get_path`)**: What is the optimal, deterministic call path between Symbol A and Symbol B?
12
+ - **Dependency Hubs (`get_dependency_hubs`)**: What are the critical architectural bottlenecks and chokepoints?
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install trishul-mcp
18
+ ```
19
+ *(Or install via `pip install llmgraph-in`)*
20
+
21
+ ## MCP Client Configuration
22
+
23
+ Add to your `claude_desktop_config.json`, Cursor MCP settings, or Windsurf configuration:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "trishul": {
29
+ "command": "trishul-mcp",
30
+ "env": {
31
+ "TRISHUL_API_KEY": "llmg_live_...",
32
+ "TRISHUL_API_URL": "https://api.llmgraph.in/v1"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ *Note: `LLMGRAPH_API_KEY` and `LLMGRAPH_API_URL` are also supported for backward compatibility.*
40
+
41
+ ## License
42
+
43
+ Apache-2.0. Developed by Brainfoods Studio.
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trishul-mcp"
7
+ version = "0.1.0"
8
+ description = "Trishul: Deterministic repository-scale code intelligence Model Context Protocol (MCP) server."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "mcp>=1.0.0",
13
+ "httpx>=0.25.0",
14
+ "pydantic>=2.0.0",
15
+ ]
16
+
17
+ [project.scripts]
18
+ trishul-mcp = "trishul_mcp.server:main"
19
+ trishul = "trishul_mcp.server:main"
20
+ llmgraph-in = "trishul_mcp.server:main"
21
+ llmgraph-mcp = "trishul_mcp.server:main"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Backward-compatible wrapper for trishul-mcp."""
2
+ from trishul_mcp import __version__
3
+ from trishul_mcp import *
@@ -0,0 +1,2 @@
1
+ """Backward-compatible wrapper for trishul_mcp.client."""
2
+ from trishul_mcp.client import *
@@ -0,0 +1,2 @@
1
+ """Backward-compatible wrapper for trishul_mcp.config."""
2
+ from trishul_mcp.config import *
@@ -0,0 +1,2 @@
1
+ """Backward-compatible wrapper for trishul_mcp.errors."""
2
+ from trishul_mcp.errors import *
@@ -0,0 +1,2 @@
1
+ """Backward-compatible wrapper for trishul_mcp.schemas."""
2
+ from trishul_mcp.schemas import *
@@ -0,0 +1,6 @@
1
+ """Backward-compatible wrapper for trishul_mcp.server."""
2
+ from trishul_mcp.server import *
3
+ from trishul_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,2 @@
1
+ """trishul-mcp: Deterministic repository-scale code intelligence MCP server."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,145 @@
1
+ """Remote HTTP client for communicating with the Trishul / LLMGRAPH hosted API."""
2
+ from typing import Any
3
+ import httpx
4
+
5
+ from .config import ClientConfig, config
6
+ from .errors import (
7
+ AuthenticationError,
8
+ AuthorizationError,
9
+ TrishulClientError,
10
+ RateLimitError,
11
+ ResourceNotFoundError,
12
+ ServerError,
13
+ )
14
+
15
+ class TrishulRemoteClient:
16
+ """Lightweight HTTP client communicating with the remote code intelligence service."""
17
+
18
+ def __init__(self, client_config: ClientConfig | None = None):
19
+ self.cfg = client_config or config
20
+ self._headers = {
21
+ "Content-Type": "application/json",
22
+ "User-Agent": "trishul-mcp/0.1.0",
23
+ "X-LLMGraph-Client": "mcp",
24
+ }
25
+ if self.cfg.api_key:
26
+ self._headers["Authorization"] = f"Bearer {self.cfg.api_key}"
27
+
28
+ def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
29
+ if response.status_code == 200:
30
+ return response.json()
31
+ elif response.status_code == 401:
32
+ raise AuthenticationError("AUTH_REQUIRED", "API key (TRISHUL_API_KEY / LLMGRAPH_API_KEY) missing, invalid, or revoked.")
33
+ elif response.status_code == 403:
34
+ raise AuthorizationError("FORBIDDEN", "Access denied for this repository.")
35
+ elif response.status_code == 404:
36
+ raise ResourceNotFoundError("NOT_FOUND", "Requested repository or symbol not found.")
37
+ elif response.status_code == 429:
38
+ err_data = response.json() if response.content else {}
39
+ msg = err_data.get("error", {}).get("message", "Request quota or rate limit exceeded.")
40
+ code = err_data.get("error", {}).get("code", "RATE_LIMITED")
41
+ raise RateLimitError(code, msg)
42
+ else:
43
+ try:
44
+ err_data = response.json()
45
+ msg = err_data.get("error", {}).get("message", f"Remote error ({response.status_code})")
46
+ code = err_data.get("error", {}).get("code", "SERVER_ERROR")
47
+ raise ServerError(code, msg)
48
+ except Exception as e:
49
+ if isinstance(e, ServerError):
50
+ raise
51
+ raise ServerError("SERVER_ERROR", f"Remote error (status {response.status_code})")
52
+
53
+ # --- Repository Lifecycle Operations ---
54
+
55
+ def connect_repository(self, repo_url: str) -> dict[str, Any]:
56
+ """Connects a repository using locally configured Git credentials without model visibility."""
57
+ url = f"{self.cfg.api_url}/repositories/connect"
58
+ payload = {"repository_url": repo_url, "git_credential": self.cfg.github_token}
59
+ with httpx.Client(timeout=180.0) as client:
60
+ resp = client.post(url, json=payload, headers=self._headers)
61
+ return self._handle_response(resp)
62
+
63
+ def sync_repository(self, repo_id: str) -> dict[str, Any]:
64
+ """Syncs an existing repository incrementally using local credentials."""
65
+ url = f"{self.cfg.api_url}/repositories/sync"
66
+ payload = {"repository_id": repo_id, "git_credential": self.cfg.github_token}
67
+ with httpx.Client(timeout=180.0) as client:
68
+ resp = client.post(url, json=payload, headers=self._headers)
69
+ return self._handle_response(resp)
70
+
71
+ def repository_status(self, repo_id: str) -> dict[str, Any]:
72
+ """Checks status of an indexed repository."""
73
+ url = f"{self.cfg.api_url}/repositories/status"
74
+ payload = {"repository_id": repo_id}
75
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
76
+ resp = client.post(url, json=payload, headers=self._headers)
77
+ return self._handle_response(resp)
78
+
79
+ def list_repositories(self) -> dict[str, Any]:
80
+ """Lists all indexed repositories owned by the API key."""
81
+ url = f"{self.cfg.api_url}/repositories/list"
82
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
83
+ resp = client.get(url, headers=self._headers)
84
+ return self._handle_response(resp)
85
+
86
+ def delete_repository(self, repo_id: str) -> dict[str, Any]:
87
+ """Deletes a repository and releases quota."""
88
+ url = f"{self.cfg.api_url}/repositories/delete"
89
+ payload = {"repository_id": repo_id}
90
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
91
+ resp = client.post(url, json=payload, headers=self._headers)
92
+ return self._handle_response(resp)
93
+
94
+ # --- Semantic Traversal Queries ---
95
+
96
+ def search_symbols(self, repo_id: str, query: str, limit: int = 10) -> dict[str, Any]:
97
+ url = f"{self.cfg.api_url}/symbols/search"
98
+ payload = {"repository_id": repo_id, "query": query, "limit": limit}
99
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
100
+ resp = client.post(url, json=payload, headers=self._headers)
101
+ return self._handle_response(resp)
102
+
103
+ def get_callers(self, repo_id: str, symbol: str, min_confidence: float | None = None) -> dict[str, Any]:
104
+ url = f"{self.cfg.api_url}/callers"
105
+ payload = {"repository_id": repo_id, "symbol": symbol}
106
+ if min_confidence is not None:
107
+ payload["min_confidence"] = min_confidence
108
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
109
+ resp = client.post(url, json=payload, headers=self._headers)
110
+ return self._handle_response(resp)
111
+
112
+ def get_impact(self, repo_id: str, symbol: str, depth: int = 3, min_confidence: float | None = None) -> dict[str, Any]:
113
+ url = f"{self.cfg.api_url}/impact"
114
+ payload = {"repository_id": repo_id, "symbol": symbol, "depth": depth}
115
+ if min_confidence is not None:
116
+ payload["min_confidence"] = min_confidence
117
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
118
+ resp = client.post(url, json=payload, headers=self._headers)
119
+ return self._handle_response(resp)
120
+
121
+ def get_blast(self, repo_id: str, symbol: str, depth: int = 3, min_confidence: float | None = None) -> dict[str, Any]:
122
+ url = f"{self.cfg.api_url}/blast"
123
+ payload = {"repository_id": repo_id, "symbol": symbol, "depth": depth}
124
+ if min_confidence is not None:
125
+ payload["min_confidence"] = min_confidence
126
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
127
+ resp = client.post(url, json=payload, headers=self._headers)
128
+ return self._handle_response(resp)
129
+
130
+ def get_path(self, repo_id: str, source: str, target: str) -> dict[str, Any]:
131
+ url = f"{self.cfg.api_url}/path"
132
+ payload = {"repository_id": repo_id, "source": source, "target": target}
133
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
134
+ resp = client.post(url, json=payload, headers=self._headers)
135
+ return self._handle_response(resp)
136
+
137
+ def get_dependency_hubs(self, repo_id: str, source: str, target: str) -> dict[str, Any]:
138
+ url = f"{self.cfg.api_url}/chokepoints"
139
+ payload = {"repository_id": repo_id, "source": source, "target": target}
140
+ with httpx.Client(timeout=self.cfg.timeout_sec) as client:
141
+ resp = client.post(url, json=payload, headers=self._headers)
142
+ return self._handle_response(resp)
143
+
144
+ # Backward-compatible alias
145
+ LLMGraphRemoteClient = TrishulRemoteClient
@@ -0,0 +1,14 @@
1
+ """Client configuration and environment variable bindings for Trishul / LLMGraph MCP."""
2
+ import os
3
+ from dataclasses import dataclass
4
+
5
+ @dataclass
6
+ class ClientConfig:
7
+ # Primary: TRISHUL_*, Fallback: LLMGRAPH_*
8
+ api_url: str = os.getenv("TRISHUL_API_URL") or os.getenv("LLMGRAPH_API_URL") or "https://api.llmgraph.in/v1"
9
+ api_key: str | None = os.getenv("TRISHUL_API_KEY") or os.getenv("LLMGRAPH_API_KEY")
10
+ github_token: str | None = os.getenv("TRISHUL_GITHUB_TOKEN") or os.getenv("LLMGRAPH_GITHUB_TOKEN")
11
+ timeout_sec: float = float(os.getenv("TRISHUL_TIMEOUT_SEC") or os.getenv("LLMGRAPH_TIMEOUT_SEC") or "30.0")
12
+ max_retries: int = int(os.getenv("TRISHUL_MAX_RETRIES") or os.getenv("LLMGRAPH_MAX_RETRIES") or "3")
13
+
14
+ config = ClientConfig()
@@ -0,0 +1,28 @@
1
+ """Standard public error types for the remote Trishul / LLMGRAPH MCP client."""
2
+ from typing import Any
3
+
4
+ class TrishulClientError(Exception):
5
+ """Base exception for Trishul MCP client errors."""
6
+ def __init__(self, code: str, message: str, details: dict[str, Any] | None = None):
7
+ super().__init__(message)
8
+ self.code = code
9
+ self.message = message
10
+ self.details = details or {}
11
+
12
+ # Backward-compatible alias
13
+ LLMGraphClientError = TrishulClientError
14
+
15
+ class AuthenticationError(TrishulClientError):
16
+ """Raised when the API key is missing, invalid, or expired."""
17
+
18
+ class AuthorizationError(TrishulClientError):
19
+ """Raised when the account lacks permissions for the requested repository."""
20
+
21
+ class ResourceNotFoundError(TrishulClientError):
22
+ """Raised when a symbol, file, or repository is not found."""
23
+
24
+ class RateLimitError(TrishulClientError):
25
+ """Raised when the request quota or rate limit is exceeded."""
26
+
27
+ class ServerError(TrishulClientError):
28
+ """Raised when the remote service encounters an error."""
@@ -0,0 +1,56 @@
1
+ """Public request and response schemas for Trishul / LLMGRAPH API endpoints."""
2
+ from typing import Any
3
+ from pydantic import BaseModel, Field
4
+
5
+ class SymbolMatch(BaseModel):
6
+ symbol_id: str
7
+ name: str
8
+ qualified_name: str
9
+ kind: str
10
+ file_path: str
11
+ line: int
12
+
13
+ class SearchResponse(BaseModel):
14
+ matches: list[SymbolMatch] = Field(default_factory=list)
15
+ did_you_mean: list[str] = Field(default_factory=list)
16
+
17
+ class AffectedCaller(BaseModel):
18
+ symbol_id: str
19
+ qualified_name: str
20
+ file_path: str
21
+ line: int
22
+ distance: int
23
+ path_confidence: float
24
+ confidence_tier: str
25
+ call_path: str
26
+
27
+ class BlastResponse(BaseModel):
28
+ root_symbol: SymbolMatch
29
+ total_affected_count: int
30
+ affected_callers: list[AffectedCaller] = Field(default_factory=list)
31
+
32
+ class PathNode(BaseModel):
33
+ qualified_name: str
34
+ file_path: str
35
+ line: int
36
+
37
+ class PathResponse(BaseModel):
38
+ source: str
39
+ target: str
40
+ hops: int
41
+ cumulative_confidence: float
42
+ weakest_link_tier: str
43
+ path_nodes: list[PathNode] = Field(default_factory=list)
44
+ path_display: str
45
+
46
+ class ChokepointSymbol(BaseModel):
47
+ symbol_id: str
48
+ qualified_name: str
49
+ file_path: str
50
+ line: int
51
+ is_mandatory_bottleneck: bool = True
52
+
53
+ class ChokepointsResponse(BaseModel):
54
+ source: str
55
+ target: str
56
+ chokepoints: list[ChokepointSymbol] = Field(default_factory=list)
@@ -0,0 +1,97 @@
1
+ """FastMCP server entry point for Trishul MCP server."""
2
+ import json
3
+ import sys
4
+ from typing import Any
5
+ try:
6
+ from mcp.server.fastmcp import FastMCP
7
+ except (ImportError, ModuleNotFoundError):
8
+ from mcp.server.mcpserver import MCPServer as FastMCP
9
+
10
+ from .client import TrishulRemoteClient
11
+ from .config import config
12
+ from .errors import TrishulClientError
13
+
14
+ mcp = FastMCP("trishul")
15
+ remote_client = TrishulRemoteClient(config)
16
+
17
+ def _safe_call(fn, *args, **kwargs) -> str:
18
+ try:
19
+ res = fn(*args, **kwargs)
20
+ return json.dumps(res, indent=2)
21
+ except TrishulClientError as e:
22
+ return json.dumps({"status": "error", "error": {"code": e.code, "message": e.message, "details": e.details}}, indent=2)
23
+ except Exception as e:
24
+ return json.dumps({"status": "error", "error": {"code": "CLIENT_ERROR", "message": "Failed to communicate with Trishul code intelligence service."}}, indent=2)
25
+
26
+ # --- Repository Lifecycle Tools ---
27
+
28
+ @mcp.tool()
29
+ def connect_repository(repository_url: str) -> str:
30
+ """Connect and index a GitHub repository using hosted static code intelligence.
31
+ Credentials (if any) are loaded automatically from the local MCP environment.
32
+ Returns an opaque repository_id to use for all subsequent graph queries."""
33
+ return _safe_call(remote_client.connect_repository, repo_url=repository_url)
34
+
35
+ @mcp.tool()
36
+ def sync_repository(repository_id: str) -> str:
37
+ """Incrementally re-sync an existing repository to ingest latest code changes.
38
+ Maintains stable symbol identities and updates call relationships."""
39
+ return _safe_call(remote_client.sync_repository, repo_id=repository_id)
40
+
41
+ @mcp.tool()
42
+ def repository_status(repository_id: str) -> str:
43
+ """Check the status, symbol count, edge count, and last sync timestamp for a repository."""
44
+ return _safe_call(remote_client.repository_status, repo_id=repository_id)
45
+
46
+ @mcp.tool()
47
+ def list_repositories() -> str:
48
+ """List all indexed repositories owned by your API key with their status and symbol counts."""
49
+ return _safe_call(remote_client.list_repositories)
50
+
51
+ @mcp.tool()
52
+ def delete_repository(repository_id: str) -> str:
53
+ """Delete an indexed repository graph and release repository quota."""
54
+ return _safe_call(remote_client.delete_repository, repo_id=repository_id)
55
+
56
+ # --- Semantic Traversal Query Tools ---
57
+
58
+ @mcp.tool()
59
+ def search_symbols(repository_id: str, query: str, limit: int = 10) -> str:
60
+ """Search for code symbols across the indexed repository graph by name or qualified identifier."""
61
+ return _safe_call(remote_client.search_symbols, repo_id=repository_id, query=query, limit=limit)
62
+
63
+ @mcp.tool()
64
+ def get_callers(repository_id: str, symbol: str, min_confidence: float | None = None) -> str:
65
+ """Get direct callers of a specific symbol with certainty tiers and relationship classifications."""
66
+ return _safe_call(remote_client.get_callers, repo_id=repository_id, symbol=symbol, min_confidence=min_confidence)
67
+
68
+ @mcp.tool()
69
+ def get_impact(repository_id: str, symbol: str, depth: int = 3, min_confidence: float | None = None) -> str:
70
+ """Compute the reverse change impact of all upstream callers affected if a target symbol changes."""
71
+ return _safe_call(remote_client.get_impact, repo_id=repository_id, symbol=symbol, depth=depth, min_confidence=min_confidence)
72
+
73
+ @mcp.tool()
74
+ def get_blast(repository_id: str, symbol: str, depth: int = 3, min_confidence: float | None = None) -> str:
75
+ """Compute the reverse blast radius of all upstream callers affected if a target symbol changes."""
76
+ return _safe_call(remote_client.get_blast, repo_id=repository_id, symbol=symbol, depth=depth, min_confidence=min_confidence)
77
+
78
+ @mcp.tool()
79
+ def get_path(repository_id: str, source: str, target: str) -> str:
80
+ """Find the single optimal execution path between two symbols using weighted certainty routing."""
81
+ return _safe_call(remote_client.get_path, repo_id=repository_id, source=source, target=target)
82
+
83
+ @mcp.tool()
84
+ def get_dependency_hubs(repository_id: str, source: str, target: str) -> str:
85
+ """Find critical dependency junction symbols between source and target."""
86
+ return _safe_call(remote_client.get_dependency_hubs, repo_id=repository_id, source=source, target=target)
87
+
88
+ def main():
89
+ transport = "stdio"
90
+ if "--transport" in sys.argv:
91
+ idx = sys.argv.index("--transport")
92
+ if idx + 1 < len(sys.argv):
93
+ transport = sys.argv[idx + 1]
94
+ mcp.run(transport=transport)
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: trishul-mcp
3
+ Version: 0.1.0
4
+ Summary: Trishul: Deterministic repository-scale code intelligence Model Context Protocol (MCP) server.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: mcp>=1.0.0
8
+ Requires-Dist: httpx>=0.25.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+
11
+ # trishul-mcp
12
+
13
+ Deterministic, repository-scale semantic code intelligence Model Context Protocol (MCP) server for AI coding agents (Claude Code, Cursor, Windsurf).
14
+
15
+ ## Overview
16
+
17
+ `trishul-mcp` connects AI coding assistants directly to a persistent, topological code intelligence graph. Instead of burning your model's context window re-reading thousands of raw files or guessing caller hierarchies with naive regex, Trishul provides deterministic answers with verified certainty:
18
+
19
+ - **Callers (`get_callers`)**: Who directly or indirectly invokes a symbol?
20
+ - **Blast Radius (`get_blast` / `get_impact`)**: What breaks upstream if this function or interface changes?
21
+ - **Execution Paths (`get_path`)**: What is the optimal, deterministic call path between Symbol A and Symbol B?
22
+ - **Dependency Hubs (`get_dependency_hubs`)**: What are the critical architectural bottlenecks and chokepoints?
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install trishul-mcp
28
+ ```
29
+ *(Or install via `pip install llmgraph-in`)*
30
+
31
+ ## MCP Client Configuration
32
+
33
+ Add to your `claude_desktop_config.json`, Cursor MCP settings, or Windsurf configuration:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "trishul": {
39
+ "command": "trishul-mcp",
40
+ "env": {
41
+ "TRISHUL_API_KEY": "llmg_live_...",
42
+ "TRISHUL_API_URL": "https://api.llmgraph.in/v1"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ *Note: `LLMGRAPH_API_KEY` and `LLMGRAPH_API_URL` are also supported for backward compatibility.*
50
+
51
+ ## License
52
+
53
+ Apache-2.0. Developed by Brainfoods Studio.
@@ -0,0 +1,21 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/llmgraph_mcp/__init__.py
4
+ src/llmgraph_mcp/client.py
5
+ src/llmgraph_mcp/config.py
6
+ src/llmgraph_mcp/errors.py
7
+ src/llmgraph_mcp/schemas.py
8
+ src/llmgraph_mcp/server.py
9
+ src/trishul_mcp/__init__.py
10
+ src/trishul_mcp/client.py
11
+ src/trishul_mcp/config.py
12
+ src/trishul_mcp/errors.py
13
+ src/trishul_mcp/schemas.py
14
+ src/trishul_mcp/server.py
15
+ src/trishul_mcp.egg-info/PKG-INFO
16
+ src/trishul_mcp.egg-info/SOURCES.txt
17
+ src/trishul_mcp.egg-info/dependency_links.txt
18
+ src/trishul_mcp.egg-info/entry_points.txt
19
+ src/trishul_mcp.egg-info/requires.txt
20
+ src/trishul_mcp.egg-info/top_level.txt
21
+ tests/test_package_boundary.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ llmgraph-in = trishul_mcp.server:main
3
+ llmgraph-mcp = trishul_mcp.server:main
4
+ trishul = trishul_mcp.server:main
5
+ trishul-mcp = trishul_mcp.server:main
@@ -0,0 +1,3 @@
1
+ mcp>=1.0.0
2
+ httpx>=0.25.0
3
+ pydantic>=2.0.0
@@ -0,0 +1,2 @@
1
+ llmgraph_mcp
2
+ trishul_mcp
@@ -0,0 +1,74 @@
1
+ """Crown-jewel leakage and package boundary verification tests for llmgraph-mcp."""
2
+ from pathlib import Path
3
+ import pytest
4
+
5
+ REPO_ROOT = Path(__file__).resolve().parent.parent
6
+
7
+ PROHIBITED_TERMS = [
8
+ "CallResolver",
9
+ "ResolutionEvidence",
10
+ "SELF_METHOD_UNIQUE",
11
+ "SUPER_MRO_EXACT",
12
+ "POLYMORPHIC_RECEIVER_SET",
13
+ "STATIC_RECEIVER_TYPE_EXACT",
14
+ "blast_symbol_id",
15
+ "find_shortest_path",
16
+ "find_top_k_paths",
17
+ "find_chokepoints",
18
+ "graph.py",
19
+ "docs/private",
20
+ "validation/",
21
+ ]
22
+
23
+ def test_package_contains_no_crown_jewel_terms():
24
+ """Verifies that no file in the distributable client package references proprietary engine internals."""
25
+ leaks = []
26
+ scanned_files = 0
27
+
28
+ for path in REPO_ROOT.rglob("*"):
29
+ if path.is_file() and not any(p in path.parts for p in [".git", ".pytest_cache", "__pycache__", ".venv"]):
30
+ scanned_files += 1
31
+ text = path.read_text(encoding="utf-8", errors="replace")
32
+ # Exclude this test file itself from term search
33
+ if path.name == "test_package_boundary.py":
34
+ continue
35
+ for term in PROHIBITED_TERMS:
36
+ if term in text:
37
+ leaks.append(f"Leakage detected in {path.relative_to(REPO_ROOT)}: contains '{term}'")
38
+
39
+ assert scanned_files > 0, "No files scanned."
40
+ assert not leaks, f"Crown-jewel leakage detected:\n" + "\n".join(leaks)
41
+
42
+ def test_pyproject_contains_no_internal_dependencies():
43
+ """Verifies that pyproject.toml does not depend on internal proprietary packages or Oroboros."""
44
+ pyproject = REPO_ROOT / "pyproject.toml"
45
+ assert pyproject.exists()
46
+ content = pyproject.read_text(encoding="utf-8")
47
+ assert "oroboros" not in content.lower(), "pyproject.toml directly references Oroboros repository."
48
+ assert "graph" not in content.lower() or "llmgraph" in content.lower(), "Unsanitized internal dependency in pyproject.toml"
49
+
50
+ def test_client_imports_are_pure_http():
51
+ """Verifies that client files only import safe standard HTTP libraries."""
52
+ import sys
53
+ sys.path.insert(0, str(REPO_ROOT / "src"))
54
+ from trishul_mcp import client as t_client, server as t_server
55
+ from llmgraph_mcp import client, config, errors, schemas, server
56
+ assert hasattr(t_server, "connect_repository")
57
+ assert hasattr(t_server, "search_symbols")
58
+ assert hasattr(t_server, "get_callers")
59
+ assert hasattr(t_server, "get_impact")
60
+ assert hasattr(t_server, "get_blast")
61
+ assert hasattr(t_server, "get_path")
62
+ assert hasattr(t_server, "get_dependency_hubs")
63
+ assert hasattr(t_client, "TrishulRemoteClient")
64
+ assert hasattr(server, "connect_repository")
65
+ assert hasattr(server, "search_symbols")
66
+ assert hasattr(server, "get_callers")
67
+ assert hasattr(server, "get_impact")
68
+ assert hasattr(server, "get_blast")
69
+ assert hasattr(server, "get_path")
70
+ assert hasattr(server, "get_dependency_hubs")
71
+ assert hasattr(client, "LLMGraphRemoteClient")
72
+ assert hasattr(client.LLMGraphRemoteClient, "get_impact")
73
+ assert hasattr(client.LLMGraphRemoteClient, "get_dependency_hubs")
74
+ assert hasattr(client.LLMGraphRemoteClient, "get_blast")