source-context-mcp 0.1.dev1__tar.gz → 0.1.dev3__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.
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/PKG-INFO +1 -1
- source_context_mcp-0.1.dev3/pyproject.toml +53 -0
- source_context_mcp-0.1.dev1/pyproject.toml → source_context_mcp-0.1.dev3/pyproject.toml.orig +1 -1
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/cli.py +10 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/api_client.py +30 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/settings.py +3 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/server.py +0 -3
- source_context_mcp-0.1.dev3/src/source_context_mcp/tools/base.py +75 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/graph.py +16 -6
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/vector.py +4 -4
- source_context_mcp-0.1.dev1/src/source_context_mcp/tools/base.py +0 -41
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/README.md +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/__init__.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/__main__.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/__init__.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/context.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/dependencies.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/py.typed +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/__init__.py +0 -0
- {source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/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.
|
|
3
|
+
Version: 0.1.dev3
|
|
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>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "source-context-mcp"
|
|
3
|
+
version = "0.1.dev3"
|
|
4
|
+
description = "Local Model Context Protocol (MCP) server for Source Context code intelligence."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx>=0.28.1",
|
|
9
|
+
"mcp[cli]>=2.0.0",
|
|
10
|
+
"pydantic>=2.13.4",
|
|
11
|
+
"pydantic-settings>=2.14.2",
|
|
12
|
+
"tomli-w>=1.2.0",
|
|
13
|
+
"typer>=0.27.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[[project.authors]]
|
|
17
|
+
name = "Le Ngoc Tu"
|
|
18
|
+
email = "tuleaibk@gmail.com"
|
|
19
|
+
|
|
20
|
+
[[project.authors]]
|
|
21
|
+
name = "Dang Huu Hieu"
|
|
22
|
+
email = "danghuuhieu038@gmail.com"
|
|
23
|
+
|
|
24
|
+
[[project.authors]]
|
|
25
|
+
name = "Huynh Phat Tai"
|
|
26
|
+
email = "huynhphtai@gmail.com"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Repository = "https://github.com/TuLe142857/source-context"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
source-context-mcp = "source_context_mcp:run"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["uv_build>=0.11.21,<0.12.0"]
|
|
36
|
+
build-backend = "uv_build"
|
|
37
|
+
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
extend = "../pyproject.toml"
|
|
40
|
+
|
|
41
|
+
[tool.mypy]
|
|
42
|
+
python_version = "3.12"
|
|
43
|
+
explicit_package_bases = true
|
|
44
|
+
mypy_path = ["src"]
|
|
45
|
+
check_untyped_defs = true
|
|
46
|
+
disallow_untyped_defs = true
|
|
47
|
+
disallow_incomplete_defs = true
|
|
48
|
+
no_implicit_optional = true
|
|
49
|
+
warn_return_any = true
|
|
50
|
+
warn_unused_ignores = true
|
|
51
|
+
warn_redundant_casts = true
|
|
52
|
+
warn_unreachable = true
|
|
53
|
+
show_error_codes = true
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
1
3
|
import typer
|
|
2
4
|
|
|
3
5
|
from .core.settings import CONFIG_FILE, get_settings, get_settings_with_overrides, write_settings
|
|
@@ -47,3 +49,11 @@ def show_config():
|
|
|
47
49
|
typer.echo(f"Configuration file: {CONFIG_FILE}")
|
|
48
50
|
typer.echo("Note: Secret value will be display as ***")
|
|
49
51
|
typer.echo(settings_json)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@cli.callback(invoke_without_command=True)
|
|
55
|
+
def version_check(
|
|
56
|
+
version: bool = typer.Option(False, "--version", "-v", help="Show version"),
|
|
57
|
+
):
|
|
58
|
+
if version:
|
|
59
|
+
typer.echo(importlib.metadata.version("source-context-mcp"))
|
|
@@ -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
|
|
|
@@ -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()
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/graph.py
RENAMED
|
@@ -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.
|
|
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}/
|
|
23
|
-
return res.
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
+
return res.result()
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/vector.py
RENAMED
|
@@ -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.
|
|
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.
|
|
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"]
|
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/__init__.py
RENAMED
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/__main__.py
RENAMED
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/__init__.py
RENAMED
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/core/context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/__init__.py
RENAMED
|
File without changes
|
{source_context_mcp-0.1.dev1 → source_context_mcp-0.1.dev3}/src/source_context_mcp/tools/debug.py
RENAMED
|
File without changes
|