source-context-mcp 0.1.dev1__tar.gz → 0.1.dev2__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.
Files changed (19) hide show
  1. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/PKG-INFO +1 -1
  2. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/pyproject.toml +1 -1
  3. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/core/api_client.py +30 -0
  4. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/core/settings.py +3 -0
  5. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/server.py +2 -0
  6. source_context_mcp-0.1.dev2/src/source_context_mcp/tools/base.py +75 -0
  7. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/tools/graph.py +16 -6
  8. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/tools/vector.py +4 -4
  9. source_context_mcp-0.1.dev1/src/source_context_mcp/tools/base.py +0 -41
  10. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/README.md +0 -0
  11. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/__init__.py +0 -0
  12. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/__main__.py +0 -0
  13. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/cli.py +0 -0
  14. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/core/__init__.py +0 -0
  15. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/core/context.py +0 -0
  16. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/core/dependencies.py +0 -0
  17. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/py.typed +0 -0
  18. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/tools/__init__.py +0 -0
  19. {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev2}/src/source_context_mcp/tools/debug.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: source-context-mcp
3
- Version: 0.1.dev1
3
+ Version: 0.1.dev2
4
4
  Summary: Local Model Context Protocol (MCP) server for Source Context code intelligence.
5
5
  Author: Le Ngoc Tu, Dang Huu Hieu, Huynh Phat Tai
6
6
  Author-email: Le Ngoc Tu <tuleaibk@gmail.com>, Dang Huu Hieu <danghuuhieu038@gmail.com>, Huynh Phat Tai <huynhphtai@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "source-context-mcp"
3
- version = "0.1.dev1"
3
+ version = "0.1.dev2"
4
4
  description = "Local Model Context Protocol (MCP) server for Source Context code intelligence."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from typing import Any, Literal, overload
5
5
 
6
6
  import httpx
7
+ from mcp.server.mcpserver.exceptions import ToolError
7
8
  from pydantic import TypeAdapter, ValidationError
8
9
 
9
10
 
@@ -15,6 +16,27 @@ class ApiResponse[T]:
15
16
  error_code: str | None
16
17
  message: str | None
17
18
 
19
+ def result(self) -> T:
20
+ """
21
+ Return the response data if the API request succeeded.
22
+
23
+ Raises:
24
+ ToolError:
25
+ If the API request failed. This exception is intended to propagate to the MCP framework, which
26
+ automatically converts it into an MCP tool error response (`isError=True`). It does not need to be
27
+ caught by tool implementations.
28
+ """
29
+ if self.success:
30
+ return self.data
31
+ if self.error_code is None and self.message is None:
32
+ msg = "UNKNOWN_ERROR"
33
+ else:
34
+ msg = (
35
+ f"{self.error_code if self.error_code is not None else ''} "
36
+ f"{self.message if self.message is not None else ''}"
37
+ )
38
+ raise ToolError(msg)
39
+
18
40
  @staticmethod
19
41
  def ok(data: T | None, message: str | None = None, status_code: int = 200) -> ApiResponse[T]:
20
42
  return ApiResponse(
@@ -112,6 +134,14 @@ class ApiClient:
112
134
  error_code="CONNECTION_ERROR",
113
135
  )
114
136
 
137
+ except httpx.HTTPStatusError as exc:
138
+ res = exc.response
139
+ try:
140
+ res_json = res.json()
141
+ return ApiResponse.error(exc.response.status_code, res_json.get("error_code", "UNKNOW_ERROR"))
142
+ except ValueError:
143
+ return ApiResponse.error(0, str(exc))
144
+
115
145
  except httpx.HTTPError as exc:
116
146
  return ApiResponse.error(0, str(exc))
117
147
 
@@ -23,6 +23,9 @@ class Settings(BaseSettings):
23
23
  DEFAULT_WORKSPACE_ID: int = 1
24
24
  """Default Workspace ID"""
25
25
 
26
+ PATH_WORKSPACE: dict[str, int] = {}
27
+ PATH_REPO: dict[str, int] = {}
28
+
26
29
  @classmethod
27
30
  def settings_customise_sources(
28
31
  cls,
@@ -37,7 +37,9 @@ def create_server(
37
37
  life_span = make_life_span(
38
38
  base_url_override=base_url_override, pat_override=pat_override, workspace_id_override=workspace_id_override
39
39
  )
40
+ import sys
40
41
 
42
+ print("hello", file=sys.stderr)
41
43
  server = MCPServer(
42
44
  name="SourceContextMCP",
43
45
  lifespan=life_span,
@@ -0,0 +1,75 @@
1
+ from typing import Any
2
+
3
+ from mcp.server import MCPServer
4
+ from mcp.server.mcpserver import Context
5
+
6
+ from source_context_mcp.core import ApiClientDep, AppContext
7
+
8
+
9
+ def register_tools(mcp: MCPServer) -> None:
10
+ """
11
+ Register bases tools:
12
+ - list user's available workspaces
13
+ - list repos in workspace
14
+ - list branches in repo
15
+ - list projects in branch
16
+
17
+ Args:
18
+ mcp: MCPServer instance
19
+ """
20
+
21
+ @mcp.tool(description="List all available workspaces that current user can access")
22
+ async def list_workspaces(client: ApiClientDep) -> Any:
23
+ res = await client.get("/general/workspaces", response_model=list)
24
+ return res.result()
25
+
26
+ @mcp.tool(
27
+ description=(
28
+ "Get the default settings for specified directory.Include default workspace id adn default repository id."
29
+ )
30
+ )
31
+ async def get_path_settings(ctx: Context[AppContext], path: str) -> dict[str, Any]:
32
+ app_context: AppContext = ctx.request_context.lifespan_context
33
+ settings = app_context.settings
34
+ return {
35
+ "path": path,
36
+ "default_workspace_id": settings.PATH_WORKSPACE.get(path, None),
37
+ "default_repo_id": settings.PATH_REPO.get(path, None),
38
+ }
39
+
40
+ @mcp.tool(
41
+ description=(
42
+ "Get default workspace. "
43
+ "This is the default workspace use settings in case current path has no default config"
44
+ )
45
+ )
46
+ async def default_workspace(ctx: Context[AppContext]) -> Any:
47
+ app_context: AppContext = ctx.request_context.lifespan_context
48
+ return app_context.settings.DEFAULT_WORKSPACE_ID
49
+
50
+ @mcp.tool(description="List all available repositories in workspace")
51
+ async def list_repositories(
52
+ client: ApiClientDep,
53
+ workspace_id: int,
54
+ ) -> Any:
55
+ res = await client.post("/general/repositories", {"workspace_id": workspace_id})
56
+ return res.result()
57
+
58
+ @mcp.tool(description="List all available branches in specified repository.")
59
+ async def list_branches(client: ApiClientDep, workspace_id: int, repository_id: int) -> Any:
60
+ req_body = {
61
+ "workspace_id": workspace_id,
62
+ "repository_id": repository_id,
63
+ }
64
+ res = await client.post("/general/branches", req_body)
65
+ return res.result()
66
+
67
+ @mcp.tool(description="List all available projects in specific branch.")
68
+ async def list_projects(client: ApiClientDep, workspace_id: int, repository_id: int, branch_name: str) -> Any:
69
+ req_body = {
70
+ "workspace_id": workspace_id,
71
+ "repo_id": repository_id,
72
+ "branch_name": branch_name,
73
+ }
74
+ res = await client.post("/general/projects", req_body)
75
+ return res.result()
@@ -15,24 +15,34 @@ def register_tools(mcp: MCPServer) -> None:
15
15
  @mcp.tool(description="List all files in specified project.")
16
16
  async def list_files_in_project(client: ApiClientDep, project_id: str) -> Any:
17
17
  res = await client.get(f"/graph/projects/{project_id}/files")
18
- return res.data
18
+ return res.result()
19
19
 
20
20
  @mcp.tool(description="Inspect file structure(classes and methods")
21
21
  async def get_file_structure(client: ApiClientDep, file_id: str) -> Any:
22
- res = await client.get(f"/graph/files/{file_id}/structures")
23
- return res.data
22
+ res = await client.get(f"/graph/files/{file_id}/structure")
23
+ return res.result()
24
24
 
25
25
  @mcp.tool(description="Read file content.")
26
26
  async def get_file_content(client: ApiClientDep, file_id: str) -> Any:
27
27
  res = await client.get(f"/graph/files/{file_id}/content")
28
- return res.data
28
+ return res.result()
29
+
30
+ @mcp.tool(description="Get node information: id, name, file_id, position in file")
31
+ async def get_node_info(client: ApiClientDep, node_id: str) -> Any:
32
+ res = await client.get(f"/graph/nodes/{node_id}")
33
+ return res.result()
34
+
35
+ @mcp.tool(description="Get node information.")
36
+ async def get_node_content(client: ApiClientDep, node_id: str) -> Any:
37
+ res = await client.get(f"/graph/nodes/{node_id}/content")
38
+ return res.result()
29
39
 
30
40
  @mcp.tool(description="Find all nodes that call/reference to the specified node.")
31
41
  async def find_node_usages(client: ApiClientDep, node_id: str) -> Any:
32
42
  res = await client.get(f"/graph/nodes/{node_id}/usages")
33
- return res.data
43
+ return res.result()
34
44
 
35
45
  @mcp.tool(description="Find all nodes(or its children) that the specified node is calling or reference to.")
36
46
  async def find_node_callees(client: ApiClientDep, node_id: str) -> Any:
37
47
  res = await client.get(f"/graph/nodes/{node_id}/callees")
38
- return res.data
48
+ return res.result()
@@ -12,7 +12,7 @@ def register_tools(mcp: MCPServer) -> None:
12
12
  mcp: MCP server instance
13
13
  """
14
14
 
15
- @mcp.tool()
15
+ @mcp.tool(description="Sematic search.")
16
16
  async def search_in_workspace(client: ApiClientDep, query: str, workspace_id: int, top_k: int = 5) -> Any:
17
17
  req_body = {
18
18
  "query": query,
@@ -21,9 +21,9 @@ def register_tools(mcp: MCPServer) -> None:
21
21
 
22
22
  res = await client.post(f"/vector/search/{workspace_id}", req_body)
23
23
 
24
- return res.data
24
+ return res.result()
25
25
 
26
- @mcp.tool()
26
+ @mcp.tool(description="Sematic search.")
27
27
  async def search_in_repo_and_branch(
28
28
  client: ApiClientDep, repository_id: int, branch_name: str, query: str, top_k: int = 5
29
29
  ) -> Any:
@@ -34,4 +34,4 @@ def register_tools(mcp: MCPServer) -> None:
34
34
 
35
35
  res = await client.post(f"/vector/search/{repository_id}/{branch_name}", req_body)
36
36
 
37
- return res.data
37
+ return res.result()
@@ -1,41 +0,0 @@
1
- from typing import Any
2
-
3
- from mcp.server import MCPServer
4
-
5
- from source_context_mcp.core import ApiClientDep
6
-
7
-
8
- def register_tools(mcp: MCPServer) -> None:
9
- """
10
- Register bases tools:
11
- - list user's available workspaces
12
- - list repos in workspace
13
- - list branches in repo
14
- - list projects in branch
15
-
16
- Args:
17
- mcp: MCPServer instance
18
- """
19
-
20
- @mcp.tool(description="List all available workspaces that current user can access")
21
- async def list_workspaces(client: ApiClientDep) -> Any:
22
- res = await client.get("/general/workspaces", response_model=list)
23
- return res.data
24
-
25
- @mcp.tool(description="List all available repositories in workspace")
26
- async def list_repositories(
27
- client: ApiClientDep,
28
- workspace_id: int,
29
- ) -> Any:
30
- res = await client.post("/general/repositories", {"workspace_id": workspace_id})
31
- return res.data
32
-
33
- @mcp.tool(description="List all available branches in specified repository.")
34
- async def list_branches(client: ApiClientDep, project_id: int) -> Any:
35
- """This tool is not implemented yet"""
36
- return ["This tool is not implemented yet"]
37
-
38
- @mcp.tool(description="List all available projects in specific branch.")
39
- def list_projects(client: ApiClientDep, branch_id: int) -> Any:
40
- """This tool is not implemented yet"""
41
- return ["This tool is not implemented yet"]