gopher-mcp-python 0.1.1__py3-none-any.whl
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.
- gopher_mcp_python/__init__.py +52 -0
- gopher_mcp_python/agent.py +267 -0
- gopher_mcp_python/config.py +172 -0
- gopher_mcp_python/errors.py +38 -0
- gopher_mcp_python/ffi/__init__.py +7 -0
- gopher_mcp_python/ffi/library.py +365 -0
- gopher_mcp_python/result.py +192 -0
- gopher_mcp_python/server_config.py +47 -0
- gopher_mcp_python-0.1.1.dist-info/METADATA +269 -0
- gopher_mcp_python-0.1.1.dist-info/RECORD +13 -0
- gopher_mcp_python-0.1.1.dist-info/WHEEL +5 -0
- gopher_mcp_python-0.1.1.dist-info/licenses/LICENSE +201 -0
- gopher_mcp_python-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ctypes interface to the gopher-mcp-python native library.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ctypes
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from ctypes import c_char_p, c_void_p, c_int32, c_int64, POINTER, Structure
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Any
|
|
11
|
+
|
|
12
|
+
# Type alias for opaque handle
|
|
13
|
+
GopherOrchHandle = c_void_p
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GopherOrchErrorInfo(Structure):
|
|
17
|
+
"""
|
|
18
|
+
Error info structure matching C:
|
|
19
|
+
typedef struct {
|
|
20
|
+
gopher_orch_error_t code;
|
|
21
|
+
const char* message;
|
|
22
|
+
const char* details;
|
|
23
|
+
const char* file;
|
|
24
|
+
int32_t line;
|
|
25
|
+
} gopher_orch_error_info_t;
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_fields_ = [
|
|
29
|
+
("code", c_int32),
|
|
30
|
+
("message", c_char_p),
|
|
31
|
+
("details", c_char_p),
|
|
32
|
+
("file", c_char_p),
|
|
33
|
+
("line", c_int32),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GopherOrchLibrary:
|
|
38
|
+
"""
|
|
39
|
+
Wrapper for the gopher-mcp-python native library using ctypes.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
_instance: Optional["GopherOrchLibrary"] = None
|
|
43
|
+
_lib: Optional[ctypes.CDLL] = None
|
|
44
|
+
_available: bool = False
|
|
45
|
+
_debug: bool = False
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self._load_library()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def get_instance(cls) -> Optional["GopherOrchLibrary"]:
|
|
52
|
+
"""
|
|
53
|
+
Get the library instance, loading it if necessary.
|
|
54
|
+
"""
|
|
55
|
+
if cls._instance is None:
|
|
56
|
+
cls._instance = GopherOrchLibrary()
|
|
57
|
+
return cls._instance if cls._instance._available else None
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def is_available(cls) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Check if the library is available.
|
|
63
|
+
"""
|
|
64
|
+
instance = cls.get_instance()
|
|
65
|
+
return instance is not None and instance._available
|
|
66
|
+
|
|
67
|
+
def _load_library(self) -> None:
|
|
68
|
+
self._debug = os.environ.get("DEBUG") is not None
|
|
69
|
+
|
|
70
|
+
library_name = self._get_library_name()
|
|
71
|
+
search_paths = self._get_search_paths()
|
|
72
|
+
|
|
73
|
+
# Try custom path from environment variable
|
|
74
|
+
env_path = os.environ.get("GOPHER_MCP_PYTHON_LIBRARY_PATH")
|
|
75
|
+
if env_path and os.path.exists(env_path):
|
|
76
|
+
try:
|
|
77
|
+
self._lib = ctypes.CDLL(env_path)
|
|
78
|
+
self._setup_functions()
|
|
79
|
+
self._available = True
|
|
80
|
+
return
|
|
81
|
+
except OSError as e:
|
|
82
|
+
if self._debug:
|
|
83
|
+
print(
|
|
84
|
+
f"Failed to load from GOPHER_MCP_PYTHON_LIBRARY_PATH: {e}",
|
|
85
|
+
file=sys.stderr,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Try search paths
|
|
89
|
+
for search_path in search_paths:
|
|
90
|
+
lib_file = os.path.join(search_path, library_name)
|
|
91
|
+
if os.path.exists(lib_file):
|
|
92
|
+
try:
|
|
93
|
+
self._lib = ctypes.CDLL(lib_file)
|
|
94
|
+
self._setup_functions()
|
|
95
|
+
self._available = True
|
|
96
|
+
return
|
|
97
|
+
except OSError as e:
|
|
98
|
+
if self._debug:
|
|
99
|
+
print(f"Failed to load from {search_path}: {e}", file=sys.stderr)
|
|
100
|
+
|
|
101
|
+
# Try loading by name (system paths)
|
|
102
|
+
try:
|
|
103
|
+
self._lib = ctypes.CDLL(library_name)
|
|
104
|
+
self._setup_functions()
|
|
105
|
+
self._available = True
|
|
106
|
+
return
|
|
107
|
+
except OSError as e:
|
|
108
|
+
if self._debug:
|
|
109
|
+
print(f"Failed to load gopher-mcp-python library: {e}", file=sys.stderr)
|
|
110
|
+
print("Searched paths:", file=sys.stderr)
|
|
111
|
+
for p in search_paths:
|
|
112
|
+
print(f" - {p}", file=sys.stderr)
|
|
113
|
+
|
|
114
|
+
self._available = False
|
|
115
|
+
|
|
116
|
+
def _setup_functions(self) -> None:
|
|
117
|
+
if self._lib is None:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# Agent functions
|
|
121
|
+
self._lib.gopher_orch_agent_create_by_json.argtypes = [
|
|
122
|
+
c_char_p,
|
|
123
|
+
c_char_p,
|
|
124
|
+
c_char_p,
|
|
125
|
+
]
|
|
126
|
+
self._lib.gopher_orch_agent_create_by_json.restype = c_void_p
|
|
127
|
+
|
|
128
|
+
self._lib.gopher_orch_agent_create_by_api_key.argtypes = [
|
|
129
|
+
c_char_p,
|
|
130
|
+
c_char_p,
|
|
131
|
+
c_char_p,
|
|
132
|
+
]
|
|
133
|
+
self._lib.gopher_orch_agent_create_by_api_key.restype = c_void_p
|
|
134
|
+
|
|
135
|
+
self._lib.gopher_orch_agent_run.argtypes = [c_void_p, c_char_p, c_int64]
|
|
136
|
+
self._lib.gopher_orch_agent_run.restype = c_char_p
|
|
137
|
+
|
|
138
|
+
self._lib.gopher_orch_agent_add_ref.argtypes = [c_void_p]
|
|
139
|
+
self._lib.gopher_orch_agent_add_ref.restype = None
|
|
140
|
+
|
|
141
|
+
self._lib.gopher_orch_agent_release.argtypes = [c_void_p]
|
|
142
|
+
self._lib.gopher_orch_agent_release.restype = None
|
|
143
|
+
|
|
144
|
+
# API functions
|
|
145
|
+
self._lib.gopher_orch_api_fetch_servers.argtypes = [c_char_p]
|
|
146
|
+
self._lib.gopher_orch_api_fetch_servers.restype = c_char_p
|
|
147
|
+
|
|
148
|
+
# Error functions
|
|
149
|
+
self._lib.gopher_orch_last_error.argtypes = []
|
|
150
|
+
self._lib.gopher_orch_last_error.restype = POINTER(GopherOrchErrorInfo)
|
|
151
|
+
|
|
152
|
+
self._lib.gopher_orch_clear_error.argtypes = []
|
|
153
|
+
self._lib.gopher_orch_clear_error.restype = None
|
|
154
|
+
|
|
155
|
+
self._lib.gopher_orch_free.argtypes = [c_void_p]
|
|
156
|
+
self._lib.gopher_orch_free.restype = None
|
|
157
|
+
|
|
158
|
+
# Logging functions (optional - may not exist in all versions)
|
|
159
|
+
try:
|
|
160
|
+
self._lib.gopher_orch_set_log_level.argtypes = [c_int32]
|
|
161
|
+
self._lib.gopher_orch_set_log_level.restype = None
|
|
162
|
+
# Set default log level to Warning (3) for production use
|
|
163
|
+
# This suppresses debug and info logs that appear during normal operation
|
|
164
|
+
self._lib.gopher_orch_set_log_level(3)
|
|
165
|
+
except AttributeError:
|
|
166
|
+
# Function not available in this version of the library
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
def _get_library_name(self) -> str:
|
|
170
|
+
if sys.platform == "darwin":
|
|
171
|
+
return "libgopher-orch.dylib"
|
|
172
|
+
elif sys.platform == "win32":
|
|
173
|
+
return "gopher-orch.dll"
|
|
174
|
+
else:
|
|
175
|
+
return "libgopher-orch.so"
|
|
176
|
+
|
|
177
|
+
def _get_platform_package_path(self) -> Optional[str]:
|
|
178
|
+
"""
|
|
179
|
+
Get the path to the platform-specific native package.
|
|
180
|
+
These packages are published as gopher-mcp-python-native-{platform}-{arch}
|
|
181
|
+
and contain the native library for that specific platform.
|
|
182
|
+
"""
|
|
183
|
+
import platform as plat
|
|
184
|
+
|
|
185
|
+
# Determine platform and architecture
|
|
186
|
+
system = sys.platform # 'darwin', 'linux', 'win32'
|
|
187
|
+
machine = plat.machine().lower() # 'arm64', 'x86_64', 'amd64'
|
|
188
|
+
|
|
189
|
+
# Map machine names to our arch names
|
|
190
|
+
arch_map = {
|
|
191
|
+
"arm64": "arm64",
|
|
192
|
+
"aarch64": "arm64",
|
|
193
|
+
"x86_64": "x64",
|
|
194
|
+
"amd64": "x64",
|
|
195
|
+
"x64": "x64",
|
|
196
|
+
}
|
|
197
|
+
arch = arch_map.get(machine)
|
|
198
|
+
if not arch:
|
|
199
|
+
if self._debug:
|
|
200
|
+
print(f"Unsupported architecture: {machine}", file=sys.stderr)
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
# Map platform names
|
|
204
|
+
platform_map = {
|
|
205
|
+
"darwin": "darwin",
|
|
206
|
+
"linux": "linux",
|
|
207
|
+
"win32": "win32",
|
|
208
|
+
}
|
|
209
|
+
platform_name = platform_map.get(system)
|
|
210
|
+
if not platform_name:
|
|
211
|
+
if self._debug:
|
|
212
|
+
print(f"Unsupported platform: {system}", file=sys.stderr)
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
# Construct the package name
|
|
216
|
+
package_name = f"gopher_mcp_python_native_{platform_name}_{arch}"
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
# Try to import the platform-specific package
|
|
220
|
+
native_pkg = __import__(package_name)
|
|
221
|
+
lib_path = native_pkg.get_lib_path()
|
|
222
|
+
if lib_path.exists():
|
|
223
|
+
if self._debug:
|
|
224
|
+
print(f"Found platform package at: {lib_path}", file=sys.stderr)
|
|
225
|
+
return str(lib_path)
|
|
226
|
+
except ImportError:
|
|
227
|
+
# Package not installed - this is expected on platforms where
|
|
228
|
+
# the package wasn't installed
|
|
229
|
+
if self._debug:
|
|
230
|
+
print(f"Platform package {package_name} not found", file=sys.stderr)
|
|
231
|
+
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def _get_search_paths(self) -> list:
|
|
235
|
+
paths = []
|
|
236
|
+
|
|
237
|
+
# 1. Try platform-specific package first (pip distribution)
|
|
238
|
+
platform_path = self._get_platform_package_path()
|
|
239
|
+
if platform_path:
|
|
240
|
+
paths.append(platform_path)
|
|
241
|
+
|
|
242
|
+
# 2. Get the directory containing this module for development fallbacks
|
|
243
|
+
module_dir = Path(__file__).parent.parent.parent
|
|
244
|
+
|
|
245
|
+
# Development paths (native/lib in various locations)
|
|
246
|
+
paths.extend([
|
|
247
|
+
# Project root native/lib
|
|
248
|
+
os.path.join(os.getcwd(), "native", "lib"),
|
|
249
|
+
# Relative to module location
|
|
250
|
+
os.path.join(module_dir, "native", "lib"),
|
|
251
|
+
os.path.join(module_dir.parent, "native", "lib"),
|
|
252
|
+
])
|
|
253
|
+
|
|
254
|
+
# 3. System paths as last resort
|
|
255
|
+
if sys.platform == "darwin":
|
|
256
|
+
paths.extend(["/usr/local/lib", "/opt/homebrew/lib"])
|
|
257
|
+
paths.append("/usr/lib")
|
|
258
|
+
|
|
259
|
+
return paths
|
|
260
|
+
|
|
261
|
+
# Agent functions
|
|
262
|
+
def agent_create_by_json(
|
|
263
|
+
self, provider: str, model: str, server_json: str
|
|
264
|
+
) -> Optional[GopherOrchHandle]:
|
|
265
|
+
"""Create an agent using JSON server configuration."""
|
|
266
|
+
if not self._available or self._lib is None:
|
|
267
|
+
return None
|
|
268
|
+
return self._lib.gopher_orch_agent_create_by_json(
|
|
269
|
+
provider.encode("utf-8"),
|
|
270
|
+
model.encode("utf-8"),
|
|
271
|
+
server_json.encode("utf-8"),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def agent_create_by_api_key(
|
|
275
|
+
self, provider: str, model: str, api_key: str
|
|
276
|
+
) -> Optional[GopherOrchHandle]:
|
|
277
|
+
"""Create an agent using API key."""
|
|
278
|
+
if not self._available or self._lib is None:
|
|
279
|
+
return None
|
|
280
|
+
return self._lib.gopher_orch_agent_create_by_api_key(
|
|
281
|
+
provider.encode("utf-8"),
|
|
282
|
+
model.encode("utf-8"),
|
|
283
|
+
api_key.encode("utf-8"),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def agent_run(
|
|
287
|
+
self, agent: GopherOrchHandle, query: str, timeout_ms: int
|
|
288
|
+
) -> Optional[str]:
|
|
289
|
+
"""Run a query against the agent."""
|
|
290
|
+
if not self._available or self._lib is None:
|
|
291
|
+
return None
|
|
292
|
+
result = self._lib.gopher_orch_agent_run(
|
|
293
|
+
agent, query.encode("utf-8"), timeout_ms
|
|
294
|
+
)
|
|
295
|
+
if result:
|
|
296
|
+
return result.decode("utf-8")
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
def agent_add_ref(self, agent: GopherOrchHandle) -> None:
|
|
300
|
+
"""Add a reference to the agent."""
|
|
301
|
+
if self._available and self._lib is not None:
|
|
302
|
+
self._lib.gopher_orch_agent_add_ref(agent)
|
|
303
|
+
|
|
304
|
+
def agent_release(self, agent: GopherOrchHandle) -> None:
|
|
305
|
+
"""Release the agent."""
|
|
306
|
+
if self._available and self._lib is not None:
|
|
307
|
+
self._lib.gopher_orch_agent_release(agent)
|
|
308
|
+
|
|
309
|
+
# API functions
|
|
310
|
+
def api_fetch_servers(self, api_key: str) -> Optional[str]:
|
|
311
|
+
"""Fetch server configuration from API."""
|
|
312
|
+
if not self._available or self._lib is None:
|
|
313
|
+
return None
|
|
314
|
+
result = self._lib.gopher_orch_api_fetch_servers(api_key.encode("utf-8"))
|
|
315
|
+
if result:
|
|
316
|
+
return result.decode("utf-8")
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
# Error functions
|
|
320
|
+
def last_error(self) -> Optional[GopherOrchErrorInfo]:
|
|
321
|
+
"""Get the last error info."""
|
|
322
|
+
if not self._available or self._lib is None:
|
|
323
|
+
return None
|
|
324
|
+
error_ptr = self._lib.gopher_orch_last_error()
|
|
325
|
+
if error_ptr and error_ptr.contents:
|
|
326
|
+
return error_ptr.contents
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
def get_last_error_message(self) -> Optional[str]:
|
|
330
|
+
"""Get the last error message."""
|
|
331
|
+
error_info = self.last_error()
|
|
332
|
+
if error_info and error_info.message:
|
|
333
|
+
return error_info.message.decode("utf-8")
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
def clear_error(self) -> None:
|
|
337
|
+
"""Clear the last error."""
|
|
338
|
+
if self._available and self._lib is not None:
|
|
339
|
+
self._lib.gopher_orch_clear_error()
|
|
340
|
+
|
|
341
|
+
def free(self, ptr: Any) -> None:
|
|
342
|
+
"""Free memory allocated by the library."""
|
|
343
|
+
if self._available and self._lib is not None:
|
|
344
|
+
self._lib.gopher_orch_free(ptr)
|
|
345
|
+
|
|
346
|
+
def set_log_level(self, level: int) -> None:
|
|
347
|
+
"""
|
|
348
|
+
Set the global log level for the native library.
|
|
349
|
+
|
|
350
|
+
Log levels:
|
|
351
|
+
0 = Debug (most verbose)
|
|
352
|
+
1 = Info
|
|
353
|
+
2 = Notice
|
|
354
|
+
3 = Warning (default for production)
|
|
355
|
+
4 = Error
|
|
356
|
+
5 = Critical
|
|
357
|
+
6 = Alert
|
|
358
|
+
7 = Emergency
|
|
359
|
+
8 = Off (no logging)
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
level: Log level (0-8)
|
|
363
|
+
"""
|
|
364
|
+
if self._available and self._lib is not None:
|
|
365
|
+
self._lib.gopher_orch_set_log_level(level)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Result classes for the Gopher Security MCP SDK.
|
|
3
|
+
|
|
4
|
+
Provides structured result objects with status, response, and metadata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentResultStatus(Enum):
|
|
12
|
+
"""Status of an agent operation."""
|
|
13
|
+
|
|
14
|
+
SUCCESS = "success"
|
|
15
|
+
ERROR = "error"
|
|
16
|
+
TIMEOUT = "timeout"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentResult:
|
|
20
|
+
"""
|
|
21
|
+
Result of an agent operation.
|
|
22
|
+
|
|
23
|
+
Contains the response, status, and optional metadata.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
response: str,
|
|
29
|
+
status: AgentResultStatus,
|
|
30
|
+
iteration_count: int = 0,
|
|
31
|
+
tokens_used: int = 0,
|
|
32
|
+
error_message: Optional[str] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Initialize an AgentResult.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
response: The agent's response text
|
|
39
|
+
status: The result status
|
|
40
|
+
iteration_count: Number of iterations performed
|
|
41
|
+
tokens_used: Number of tokens consumed
|
|
42
|
+
error_message: Error message if status is ERROR or TIMEOUT
|
|
43
|
+
"""
|
|
44
|
+
self._response = response
|
|
45
|
+
self._status = status
|
|
46
|
+
self._iteration_count = iteration_count
|
|
47
|
+
self._tokens_used = tokens_used
|
|
48
|
+
self._error_message = error_message
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def response(self) -> str:
|
|
52
|
+
"""Get the response text."""
|
|
53
|
+
return self._response
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def status(self) -> AgentResultStatus:
|
|
57
|
+
"""Get the result status."""
|
|
58
|
+
return self._status
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def iteration_count(self) -> int:
|
|
62
|
+
"""Get the number of iterations."""
|
|
63
|
+
return self._iteration_count
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def tokens_used(self) -> int:
|
|
67
|
+
"""Get the number of tokens used."""
|
|
68
|
+
return self._tokens_used
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def error_message(self) -> Optional[str]:
|
|
72
|
+
"""Get the error message if any."""
|
|
73
|
+
return self._error_message
|
|
74
|
+
|
|
75
|
+
def is_success(self) -> bool:
|
|
76
|
+
"""Check if the result is successful."""
|
|
77
|
+
return self._status == AgentResultStatus.SUCCESS
|
|
78
|
+
|
|
79
|
+
def is_error(self) -> bool:
|
|
80
|
+
"""Check if the result is an error."""
|
|
81
|
+
return self._status == AgentResultStatus.ERROR
|
|
82
|
+
|
|
83
|
+
def is_timeout(self) -> bool:
|
|
84
|
+
"""Check if the result is a timeout."""
|
|
85
|
+
return self._status == AgentResultStatus.TIMEOUT
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def success(
|
|
89
|
+
response: str, iteration_count: int = 1, tokens_used: int = 0
|
|
90
|
+
) -> "AgentResult":
|
|
91
|
+
"""
|
|
92
|
+
Create a successful result.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
response: The response text
|
|
96
|
+
iteration_count: Number of iterations
|
|
97
|
+
tokens_used: Number of tokens used
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
AgentResult with SUCCESS status
|
|
101
|
+
"""
|
|
102
|
+
return AgentResult(
|
|
103
|
+
response=response,
|
|
104
|
+
status=AgentResultStatus.SUCCESS,
|
|
105
|
+
iteration_count=iteration_count,
|
|
106
|
+
tokens_used=tokens_used,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def error(message: str) -> "AgentResult":
|
|
111
|
+
"""
|
|
112
|
+
Create an error result.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
message: The error message
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
AgentResult with ERROR status
|
|
119
|
+
"""
|
|
120
|
+
return AgentResult(
|
|
121
|
+
response="",
|
|
122
|
+
status=AgentResultStatus.ERROR,
|
|
123
|
+
error_message=message,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def timeout(message: str = "Operation timed out") -> "AgentResult":
|
|
128
|
+
"""
|
|
129
|
+
Create a timeout result.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
message: The timeout message
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
AgentResult with TIMEOUT status
|
|
136
|
+
"""
|
|
137
|
+
return AgentResult(
|
|
138
|
+
response="",
|
|
139
|
+
status=AgentResultStatus.TIMEOUT,
|
|
140
|
+
error_message=message,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def builder() -> "AgentResultBuilder":
|
|
145
|
+
"""Create a new result builder."""
|
|
146
|
+
return AgentResultBuilder()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class AgentResultBuilder:
|
|
150
|
+
"""Builder for AgentResult."""
|
|
151
|
+
|
|
152
|
+
def __init__(self) -> None:
|
|
153
|
+
self._response: str = ""
|
|
154
|
+
self._status: AgentResultStatus = AgentResultStatus.SUCCESS
|
|
155
|
+
self._iteration_count: int = 0
|
|
156
|
+
self._tokens_used: int = 0
|
|
157
|
+
self._error_message: Optional[str] = None
|
|
158
|
+
|
|
159
|
+
def response(self, response: str) -> "AgentResultBuilder":
|
|
160
|
+
"""Set the response text."""
|
|
161
|
+
self._response = response
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def status(self, status: AgentResultStatus) -> "AgentResultBuilder":
|
|
165
|
+
"""Set the status."""
|
|
166
|
+
self._status = status
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def iteration_count(self, count: int) -> "AgentResultBuilder":
|
|
170
|
+
"""Set the iteration count."""
|
|
171
|
+
self._iteration_count = count
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def tokens_used(self, tokens: int) -> "AgentResultBuilder":
|
|
175
|
+
"""Set the tokens used."""
|
|
176
|
+
self._tokens_used = tokens
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def error_message(self, message: str) -> "AgentResultBuilder":
|
|
180
|
+
"""Set the error message."""
|
|
181
|
+
self._error_message = message
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def build(self) -> AgentResult:
|
|
185
|
+
"""Build the AgentResult."""
|
|
186
|
+
return AgentResult(
|
|
187
|
+
response=self._response,
|
|
188
|
+
status=self._status,
|
|
189
|
+
iteration_count=self._iteration_count,
|
|
190
|
+
tokens_used=self._tokens_used,
|
|
191
|
+
error_message=self._error_message,
|
|
192
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Server configuration utilities for the Gopher Security MCP SDK.
|
|
3
|
+
|
|
4
|
+
Provides utilities for fetching and managing MCP server configurations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from gopher_mcp_python.ffi import GopherOrchLibrary
|
|
9
|
+
from gopher_mcp_python.errors import ApiKeyError, ConnectionError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServerConfig:
|
|
13
|
+
"""
|
|
14
|
+
Utility class for fetching server configurations.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def fetch(api_key: str) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Fetch server configuration from the API.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
api_key: API key for authentication
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
JSON string containing server configuration
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ApiKeyError: If API key is invalid
|
|
30
|
+
ConnectionError: If fetch fails
|
|
31
|
+
"""
|
|
32
|
+
if not api_key:
|
|
33
|
+
raise ApiKeyError("API key is required")
|
|
34
|
+
|
|
35
|
+
lib = GopherOrchLibrary.get_instance()
|
|
36
|
+
if lib is None:
|
|
37
|
+
raise ConnectionError("Native library not available")
|
|
38
|
+
|
|
39
|
+
result = lib.api_fetch_servers(api_key)
|
|
40
|
+
if result is None:
|
|
41
|
+
error_msg = lib.get_last_error_message()
|
|
42
|
+
lib.clear_error()
|
|
43
|
+
if error_msg and "api key" in error_msg.lower():
|
|
44
|
+
raise ApiKeyError(error_msg)
|
|
45
|
+
raise ConnectionError(error_msg or "Failed to fetch server configuration")
|
|
46
|
+
|
|
47
|
+
return result
|