cevheri commited on
Commit
83b2593
·
unverified ·
2 Parent(s): b823bbe006b4a8

Merge pull request #20 from lokumai/feat/auth

Browse files
Files changed (4) hide show
  1. README.md +36 -18
  2. main.py +14 -18
  3. utils/auth.py +34 -0
  4. utils/config.py +16 -1
README.md CHANGED
@@ -13,37 +13,44 @@ short_description: Defect Solver - MCP Server
13
  This project implements a Model Context Protocol (MCP) server for defect solver bug localization using FastMCP. It provides multiple bug localization tools that forward bug report data to remote endpoints for prediction and analysis.
14
 
15
  ## Features
 
16
  - Exposes three MCP tools for bug localization:
17
- - `multi_module_bug_localization`: Localizes bugs across the entire microservice architecture
18
- - `single_module_bug_localization`: Localizes bugs within a specific module
19
- - `search_space_routing`: Identifies likely microservices (search spaces) for a reported bug
20
  - Forwards issue data (key, summary, description, and optionally module) to configurable remote endpoints
21
  - Returns predicted source code locations and detailed workflow prompts
22
  - Uses async HTTP requests and robust error handling
23
  - Includes workflow and prompt templates for end-to-end bug localization and resolution
24
 
25
  ## Requirements
 
26
  - Python >= 3.13
27
  - See `pyproject.toml` for dependencies:
28
- - fastmcp
29
- - httpx
30
- - loguru
31
- - pydantic
32
- - dotenv
33
- - uv
34
 
35
- ### Note: The tools implemented in this server require a DEFECT SOLVER API KEY to send requests. To get your key, please contact [Lokum AI](https://github.com/lokumai).
36
 
37
  ## Setup
 
38
  1. Clone the repository:
 
39
  ```sh
40
  git clone <your-repo-url>
41
  cd defect_solver_mcp_server
42
  ```
 
43
  2. Install dependencies using uv:
 
44
  ```sh
45
  uv sync
46
  ```
 
47
  3. Configure environment variables:
48
  - Copy `.env.example` to `.env` and set the following as needed:
49
  - `DS_API_BASE_URL`
@@ -54,32 +61,41 @@ This project implements a Model Context Protocol (MCP) server for defect solver
54
  - `TIMEOUT` (optional)
55
 
56
  ## Usage
 
57
  Run the MCP server:
 
58
  ```sh
59
  python main.py
60
  ```
 
61
  The server will start and expose the bug localization tools via MCP.
62
 
63
  ## Available Tools
 
64
  - **multi_module_bug_localization**: Use when the responsible microservice is unknown. Returns likely microservices and localized files.
65
  - **single_module_bug_localization**: Use when the affected module is known. Returns localized files within the specified module.
66
  - **search_space_routing**: Use to identify the most likely microservices (search spaces) for a reported bug.
67
 
68
  ## Prompts & Workflow
 
69
  The server provides prompt templates for:
 
70
  - Tool selection and workflow planning
71
  - Bug report augmentation
72
  - Bug localization and result explanation
73
  - Automated bug fixing and full resolution workflow
74
 
75
- ### NOTE: There is a `chatmode.md` file personalized for DNext development that provides detailed instructions and rules on how to use the MCP server with chat-based interactions in IDEs like VSCode, Cursor, Windsurf and etc. Please refer to the [dnext-chatmode.md](https://github.com/pia-team/defect_solver_api/blob/main/.github/chatmodes/dnext-defect-solver.chatmode.md) for more information.
76
 
77
  ## Configuration
 
78
  - API base URL and endpoints are configured in `utils/config.py` and via environment variables:
79
- - `DS_API_BASE_URL`, `DS_API_MULTIMODULE_ENDPOINT`, `DS_API_SINGLEMODULE_ENDPOINT`, `DS_API_SEARCHSPACE_ENDPOINT`
80
  - Timeout and HuggingFace access token are also configurable.
81
- -
 
82
  ## MCP Inspector
 
83
  The MCP Inspector is a tool to help you visualize and debug the interactions with the MCP server.
84
  To use the MCP Inspector, simply run the command below in your terminal:
85
 
@@ -92,6 +108,7 @@ The GUI will open in your default web browser, allowing you to monitor the commu
92
  <img src="https://raw.githubusercontent.com/modelcontextprotocol/inspector/main/mcp-inspector.png" alt="MCP Inspector">
93
 
94
  ## Project Structure
 
95
  ```
96
  main.py # Entry point and MCP server with tool and prompt definitions
97
  utils/config.py # API configuration and environment variable loading
@@ -102,9 +119,10 @@ README.md # Project documentation
102
  ```
103
 
104
  ## `mcp.json` Configuration Example for Client Usage
 
105
  To use this MCP server in a client application that supports MCP, you add the config below to `mcp.json`.
106
 
107
- #### PRIVATE MCP SERVER: This config is required for when deploying the MCP server in a private Hugging Face Space that requires authentication.
108
 
109
  ```json
110
  {
@@ -113,7 +131,7 @@ To use this MCP server in a client application that supports MCP, you add the co
113
  "url": "https://dnext-ds-mcp-server.hf.space/mcp/",
114
  "type": "http",
115
  "headers": {
116
- "DS-API-Key": "${input:defect-solver-api-key}",
117
  "Authorization": "Bearer ${input:hf-access-token}"
118
  }
119
  }
@@ -135,7 +153,7 @@ To use this MCP server in a client application that supports MCP, you add the co
135
  }
136
  ```
137
 
138
- #### PUBLIC MCP SERVER: For public spaces, you can omit the `hf-access-token` input or `Authorization` header.
139
 
140
  ```json
141
  {
@@ -144,7 +162,7 @@ To use this MCP server in a client application that supports MCP, you add the co
144
  "url": "https://dnext-ds-mcp-server.hf.space/mcp/",
145
  "type": "http",
146
  "headers": {
147
- "DS-API-Key": "${input:defect-solver-api-key}"
148
  }
149
  }
150
  },
@@ -157,4 +175,4 @@ To use this MCP server in a client application that supports MCP, you add the co
157
  }
158
  ]
159
  }
160
- ```
 
13
  This project implements a Model Context Protocol (MCP) server for defect solver bug localization using FastMCP. It provides multiple bug localization tools that forward bug report data to remote endpoints for prediction and analysis.
14
 
15
  ## Features
16
+
17
  - Exposes three MCP tools for bug localization:
18
+ - `multi_module_bug_localization`: Localizes bugs across the entire microservice architecture
19
+ - `single_module_bug_localization`: Localizes bugs within a specific module
20
+ - `search_space_routing`: Identifies likely microservices (search spaces) for a reported bug
21
  - Forwards issue data (key, summary, description, and optionally module) to configurable remote endpoints
22
  - Returns predicted source code locations and detailed workflow prompts
23
  - Uses async HTTP requests and robust error handling
24
  - Includes workflow and prompt templates for end-to-end bug localization and resolution
25
 
26
  ## Requirements
27
+
28
  - Python >= 3.13
29
  - See `pyproject.toml` for dependencies:
30
+ - fastmcp
31
+ - httpx
32
+ - loguru
33
+ - pydantic
34
+ - dotenv
35
+ - uv
36
 
37
+ ### Note: The tools implemented in this server require a DEFECT SOLVER API KEY to send requests. To get your key, please contact [Lokum AI](https://github.com/lokumai)
38
 
39
  ## Setup
40
+
41
  1. Clone the repository:
42
+
43
  ```sh
44
  git clone <your-repo-url>
45
  cd defect_solver_mcp_server
46
  ```
47
+
48
  2. Install dependencies using uv:
49
+
50
  ```sh
51
  uv sync
52
  ```
53
+
54
  3. Configure environment variables:
55
  - Copy `.env.example` to `.env` and set the following as needed:
56
  - `DS_API_BASE_URL`
 
61
  - `TIMEOUT` (optional)
62
 
63
  ## Usage
64
+
65
  Run the MCP server:
66
+
67
  ```sh
68
  python main.py
69
  ```
70
+
71
  The server will start and expose the bug localization tools via MCP.
72
 
73
  ## Available Tools
74
+
75
  - **multi_module_bug_localization**: Use when the responsible microservice is unknown. Returns likely microservices and localized files.
76
  - **single_module_bug_localization**: Use when the affected module is known. Returns localized files within the specified module.
77
  - **search_space_routing**: Use to identify the most likely microservices (search spaces) for a reported bug.
78
 
79
  ## Prompts & Workflow
80
+
81
  The server provides prompt templates for:
82
+
83
  - Tool selection and workflow planning
84
  - Bug report augmentation
85
  - Bug localization and result explanation
86
  - Automated bug fixing and full resolution workflow
87
 
88
+ ### NOTE: There is a `chatmode.md` file personalized for DNext development that provides detailed instructions and rules on how to use the MCP server with chat-based interactions in IDEs like VSCode, Cursor, Windsurf and etc. Please refer to the [dnext-chatmode.md](https://github.com/pia-team/defect_solver_api/blob/main/.github/chatmodes/dnext-defect-solver.chatmode.md) for more information
89
 
90
  ## Configuration
91
+
92
  - API base URL and endpoints are configured in `utils/config.py` and via environment variables:
93
+ - `DS_API_BASE_URL`, `DS_API_MULTIMODULE_ENDPOINT`, `DS_API_SINGLEMODULE_ENDPOINT`, `DS_API_SEARCHSPACE_ENDPOINT`
94
  - Timeout and HuggingFace access token are also configurable.
95
+ -
96
+
97
  ## MCP Inspector
98
+
99
  The MCP Inspector is a tool to help you visualize and debug the interactions with the MCP server.
100
  To use the MCP Inspector, simply run the command below in your terminal:
101
 
 
108
  <img src="https://raw.githubusercontent.com/modelcontextprotocol/inspector/main/mcp-inspector.png" alt="MCP Inspector">
109
 
110
  ## Project Structure
111
+
112
  ```
113
  main.py # Entry point and MCP server with tool and prompt definitions
114
  utils/config.py # API configuration and environment variable loading
 
119
  ```
120
 
121
  ## `mcp.json` Configuration Example for Client Usage
122
+
123
  To use this MCP server in a client application that supports MCP, you add the config below to `mcp.json`.
124
 
125
+ #### PRIVATE MCP SERVER: This config is required for when deploying the MCP server in a private Hugging Face Space that requires authentication
126
 
127
  ```json
128
  {
 
131
  "url": "https://dnext-ds-mcp-server.hf.space/mcp/",
132
  "type": "http",
133
  "headers": {
134
+ "DS-API-KEY": "${input:defect-solver-api-key}",
135
  "Authorization": "Bearer ${input:hf-access-token}"
136
  }
137
  }
 
153
  }
154
  ```
155
 
156
+ #### PUBLIC MCP SERVER: For public spaces, you can omit the `hf-access-token` input or `Authorization` header
157
 
158
  ```json
159
  {
 
162
  "url": "https://dnext-ds-mcp-server.hf.space/mcp/",
163
  "type": "http",
164
  "headers": {
165
+ "DS-API-KEY": "${input:defect-solver-api-key}"
166
  }
167
  }
168
  },
 
175
  }
176
  ]
177
  }
178
+ ```
main.py CHANGED
@@ -3,13 +3,13 @@ from contextlib import asynccontextmanager
3
  import httpx
4
  from fastmcp import FastMCP, Context
5
  from loguru import logger
6
- from dataclasses import dataclass
7
  import uuid
8
  import os
9
  import sys
10
 
11
  from utils.config import API_CONFIG
12
  from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest
 
13
 
14
  from fastmcp.server.dependencies import get_http_request
15
  from starlette.requests import Request
@@ -19,18 +19,15 @@ from dotenv import load_dotenv
19
 
20
  load_dotenv()
21
 
22
-
23
- @dataclass
24
- class DSContext:
25
- """Typed context for DS MCP server"""
26
-
27
- client: httpx.AsyncClient
28
-
29
-
30
  @asynccontextmanager
31
  async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]:
32
  client = httpx.AsyncClient(timeout=API_CONFIG["timeout"])
33
- ctx = DSContext(client=client)
 
 
 
 
 
34
 
35
  try:
36
  yield ctx
@@ -70,9 +67,8 @@ async def multi_module_bug_localization(request: MultiModuleRequest, ctx: Contex
70
 
71
  # Get per-user API key from MCP Context
72
  user_request: Request = get_http_request()
73
- logger.info("Received request headers: {}".format(user_request.headers))
74
- defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
75
- logger.info("defect_solver_api_key: {}".format(defect_solver_api_key) )
76
  logger.info(
77
  "Using API key: {}".format(defect_solver_api_key)
78
  if defect_solver_api_key
@@ -82,7 +78,7 @@ async def multi_module_bug_localization(request: MultiModuleRequest, ctx: Contex
82
  headers = {
83
  "Content-Type": "application/json",
84
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
85
- "DS-API-Key": defect_solver_api_key,
86
  }
87
  headers = {k: v for k, v in headers.items() if v is not None}
88
 
@@ -153,7 +149,7 @@ async def single_module_bug_localization(request: SingleModuleRequest, ctx: Cont
153
 
154
  # Get per-user API key from MCP Context
155
  user_request: Request = get_http_request()
156
- defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
157
  logger.info(
158
  "Using API key: {}".format(defect_solver_api_key)
159
  if defect_solver_api_key
@@ -163,7 +159,7 @@ async def single_module_bug_localization(request: SingleModuleRequest, ctx: Cont
163
  headers = {
164
  "Content-Type": "application/json",
165
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
166
- "DS-API-Key": defect_solver_api_key,
167
  }
168
  headers = {k: v for k, v in headers.items() if v is not None}
169
 
@@ -233,7 +229,7 @@ async def search_space_routing(request: SearchSpaceRoutingRequest, ctx: Context)
233
 
234
  # Get per-user API key from MCP Context
235
  user_request: Request = get_http_request()
236
- defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
237
  logger.info(
238
  "Using API key: {}".format(defect_solver_api_key)
239
  if defect_solver_api_key
@@ -243,7 +239,7 @@ async def search_space_routing(request: SearchSpaceRoutingRequest, ctx: Context)
243
  headers = {
244
  "Content-Type": "application/json",
245
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
246
- "DS-API-Key": defect_solver_api_key,
247
  }
248
  headers = {k: v for k, v in headers.items() if v is not None}
249
 
 
3
  import httpx
4
  from fastmcp import FastMCP, Context
5
  from loguru import logger
 
6
  import uuid
7
  import os
8
  import sys
9
 
10
  from utils.config import API_CONFIG
11
  from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest
12
+ from utils.auth import DSContext, authenticate
13
 
14
  from fastmcp.server.dependencies import get_http_request
15
  from starlette.requests import Request
 
19
 
20
  load_dotenv()
21
 
 
 
 
 
 
 
 
 
22
  @asynccontextmanager
23
  async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]:
24
  client = httpx.AsyncClient(timeout=API_CONFIG["timeout"])
25
+ token = await authenticate()
26
+
27
+ if token:
28
+ client.headers.update({"Authorization": f"Bearer {token}"})
29
+
30
+ ctx = DSContext(client=client, access_token=token)
31
 
32
  try:
33
  yield ctx
 
67
 
68
  # Get per-user API key from MCP Context
69
  user_request: Request = get_http_request()
70
+
71
+ defect_solver_api_key = user_request.headers.get("DS-API-KEY", None)
 
72
  logger.info(
73
  "Using API key: {}".format(defect_solver_api_key)
74
  if defect_solver_api_key
 
78
  headers = {
79
  "Content-Type": "application/json",
80
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
81
+ "DS-API-KEY": defect_solver_api_key,
82
  }
83
  headers = {k: v for k, v in headers.items() if v is not None}
84
 
 
149
 
150
  # Get per-user API key from MCP Context
151
  user_request: Request = get_http_request()
152
+ defect_solver_api_key = user_request.headers.get("DS-API-KEY", None)
153
  logger.info(
154
  "Using API key: {}".format(defect_solver_api_key)
155
  if defect_solver_api_key
 
159
  headers = {
160
  "Content-Type": "application/json",
161
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
162
+ "DS-API-KEY": defect_solver_api_key,
163
  }
164
  headers = {k: v for k, v in headers.items() if v is not None}
165
 
 
229
 
230
  # Get per-user API key from MCP Context
231
  user_request: Request = get_http_request()
232
+ defect_solver_api_key = user_request.headers.get("DS-API-KEY", None)
233
  logger.info(
234
  "Using API key: {}".format(defect_solver_api_key)
235
  if defect_solver_api_key
 
239
  headers = {
240
  "Content-Type": "application/json",
241
  "Authorization": f"Bearer {hf_token}" if hf_token else None,
242
+ "DS-API-KEY": defect_solver_api_key,
243
  }
244
  headers = {k: v for k, v in headers.items() if v is not None}
245
 
utils/auth.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import httpx
3
+ from dataclasses import dataclass
4
+
5
+ from utils.config import KEYCLOAK_CONFIG, TOKEN_ENDPOINT
6
+
7
+
8
+ @dataclass
9
+ class DSContext:
10
+ """Typed context for DS MCP server"""
11
+
12
+ client: httpx.AsyncClient
13
+
14
+
15
+
16
+ async def authenticate() -> Optional[str]:
17
+ """Authenticate with Keycloak and return access token"""
18
+ data = {
19
+ "grant_type": KEYCLOAK_CONFIG["grant_type"],
20
+ "client_id": KEYCLOAK_CONFIG["client_id"],
21
+ "username": KEYCLOAK_CONFIG["username"],
22
+ "password": KEYCLOAK_CONFIG["password"],
23
+ }
24
+
25
+ if KEYCLOAK_CONFIG["client_secret"]:
26
+ data["client_secret"] = KEYCLOAK_CONFIG["client_secret"]
27
+
28
+ async with httpx.AsyncClient() as client:
29
+ response = await client.post(TOKEN_ENDPOINT, data=data)
30
+ if response.status_code == 200:
31
+ return response.json().get("access_token")
32
+ else:
33
+ print(f"Authentication failed: {response.status_code} - {response.text}")
34
+ return None
utils/config.py CHANGED
@@ -11,4 +11,19 @@ API_CONFIG = {
11
  "api_searchspace_endpoint": str(os.getenv("DS_API_SEARCHSPACE_ENDPOINT", "/mcp_search_space_routing")),
12
  "hf_access_token": os.getenv("HF_ACCESS_TOKEN", None),
13
  "timeout": int(os.getenv("TIMEOUT", 120)),
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  "api_searchspace_endpoint": str(os.getenv("DS_API_SEARCHSPACE_ENDPOINT", "/mcp_search_space_routing")),
12
  "hf_access_token": os.getenv("HF_ACCESS_TOKEN", None),
13
  "timeout": int(os.getenv("TIMEOUT", 120)),
14
+ }
15
+
16
+
17
+ # Keycloak Configuration
18
+ KEYCLOAK_CONFIG = {
19
+ "base_url": os.getenv("KEYCLOAK_BASE_URL", "https://training.test.orbitant.dev"),
20
+ "realm": os.getenv("KEYCLOAK_REALM", "orbitant-realm"),
21
+ "client_id": os.getenv("KEYCLOAK_CLIENT_ID", "orbitant-backend-client"),
22
+ "client_secret": os.getenv("KEYCLOAK_CLIENT_SECRET", " "),
23
+ "grant_type": os.getenv("KEYCLOAK_GRANT_TYPE", "password"),
24
+ "username": os.getenv("KEYCLOAK_USERNAME", "admin"),
25
+ "password": os.getenv("KEYCLOAK_PASSWORD", "admin"),
26
+ }
27
+
28
+ # Token Endpoint
29
+ TOKEN_ENDPOINT = f"{KEYCLOAK_CONFIG['base_url']}/realms/{KEYCLOAK_CONFIG['realm']}/protocol/openid-connect/token"