gopher-mcp-python 0.1.16__tar.gz → 0.1.23__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.
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/PKG-INFO +1 -1
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/__init__.py +3 -2
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/agent.py +157 -1
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/config.py +11 -1
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/library.py +148 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/PKG-INFO +1 -1
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/SOURCES.txt +1 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/pyproject.toml +1 -1
- gopher_mcp_python-0.1.23/tests/test_agent_create_by.py +90 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/LICENSE +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/README.md +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/__init__.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/errors.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/gopher_auth.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/scope_helpers.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/errors.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/__init__.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/__init__.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auth_client.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auto_refresh.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/config_loader.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/loader.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/oauth_client.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/session_manager.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/types.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/validation_options.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/result.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/server_config.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/dependency_links.txt +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/requires.txt +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/top_level.txt +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/setup.cfg +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/setup.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_config.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_ffi.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_gopher_auth.py +0 -0
- {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_result.py +0 -0
|
@@ -32,7 +32,7 @@ from gopher_mcp_python.errors import (
|
|
|
32
32
|
TimeoutError,
|
|
33
33
|
)
|
|
34
34
|
from gopher_mcp_python.server_config import ServerConfig
|
|
35
|
-
from gopher_mcp_python.ffi import GopherOrchLibrary
|
|
35
|
+
from gopher_mcp_python.ffi import GopherOrchLibrary, GopherOrchHandle
|
|
36
36
|
|
|
37
37
|
# Auth module re-exports
|
|
38
38
|
from gopher_mcp_python.ffi.auth import (
|
|
@@ -50,7 +50,7 @@ from gopher_mcp_python.ffi.auth import (
|
|
|
50
50
|
is_auth_available,
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
__version__ = "0.1.
|
|
53
|
+
__version__ = "0.1.23"
|
|
54
54
|
|
|
55
55
|
__all__ = [
|
|
56
56
|
# Main classes
|
|
@@ -68,6 +68,7 @@ __all__ = [
|
|
|
68
68
|
"TimeoutError",
|
|
69
69
|
# FFI
|
|
70
70
|
"GopherOrchLibrary",
|
|
71
|
+
"GopherOrchHandle",
|
|
71
72
|
# Auth
|
|
72
73
|
"GopherAuthError",
|
|
73
74
|
"ValidationResult",
|
|
@@ -27,7 +27,7 @@ Example with context manager:
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
import atexit
|
|
30
|
-
from typing import Optional
|
|
30
|
+
from typing import Callable, Optional
|
|
31
31
|
|
|
32
32
|
from gopher_mcp_python.config import GopherAgentConfig
|
|
33
33
|
from gopher_mcp_python.result import AgentResult, AgentResultStatus
|
|
@@ -183,6 +183,162 @@ class GopherAgent:
|
|
|
183
183
|
.build()
|
|
184
184
|
)
|
|
185
185
|
|
|
186
|
+
@staticmethod
|
|
187
|
+
def create_with_server_id(
|
|
188
|
+
provider: str, model: str, api_key: str, server_id: str
|
|
189
|
+
) -> "GopherAgent":
|
|
190
|
+
"""
|
|
191
|
+
Create a new GopherAgent scoped to a single MCP server by id.
|
|
192
|
+
|
|
193
|
+
Fetches server config from the Gopher API using the Bearer api key,
|
|
194
|
+
appending "?serverId={server_id}" so the response carries only the
|
|
195
|
+
matching MCP server entry.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
provider: Provider name (e.g., "AnthropicProvider")
|
|
199
|
+
model: Model identifier accepted by the chosen provider
|
|
200
|
+
api_key: Gopher API key
|
|
201
|
+
server_id: MCP server id to scope the agent to
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
GopherAgent instance
|
|
205
|
+
"""
|
|
206
|
+
return GopherAgent._create_from_ffi(
|
|
207
|
+
lambda lib: lib.agent_create_by_server_id(
|
|
208
|
+
provider, model, api_key, server_id
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def create_with_server_name(
|
|
214
|
+
provider: str, model: str, api_key: str, server_name: str
|
|
215
|
+
) -> "GopherAgent":
|
|
216
|
+
"""
|
|
217
|
+
Create a new GopherAgent scoped to a single MCP server by name.
|
|
218
|
+
|
|
219
|
+
Fetches server config from the Gopher API using the Bearer api key,
|
|
220
|
+
appending "?serverName={server_name}" so the response carries only
|
|
221
|
+
the matching MCP server entry.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
provider: Provider name (e.g., "AnthropicProvider")
|
|
225
|
+
model: Model identifier accepted by the chosen provider
|
|
226
|
+
api_key: Gopher API key
|
|
227
|
+
server_name: MCP server name to scope the agent to
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
GopherAgent instance
|
|
231
|
+
"""
|
|
232
|
+
return GopherAgent._create_from_ffi(
|
|
233
|
+
lambda lib: lib.agent_create_by_server_name(
|
|
234
|
+
provider, model, api_key, server_name
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def create_with_gateway_id(
|
|
240
|
+
provider: str, model: str, api_key: str, gateway_id: str
|
|
241
|
+
) -> "GopherAgent":
|
|
242
|
+
"""
|
|
243
|
+
Create a new GopherAgent scoped to a single MCP gateway by id.
|
|
244
|
+
|
|
245
|
+
Fetches server config from the Gopher API using the Bearer api key,
|
|
246
|
+
appending "?gatewayId={gateway_id}" so the response carries the
|
|
247
|
+
backing MCP servers for that gateway.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
provider: Provider name (e.g., "AnthropicProvider")
|
|
251
|
+
model: Model identifier accepted by the chosen provider
|
|
252
|
+
api_key: Gopher API key
|
|
253
|
+
gateway_id: MCP gateway id to scope the agent to
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
GopherAgent instance
|
|
257
|
+
"""
|
|
258
|
+
return GopherAgent._create_from_ffi(
|
|
259
|
+
lambda lib: lib.agent_create_by_gateway_id(
|
|
260
|
+
provider, model, api_key, gateway_id
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def create_with_gateway_name(
|
|
266
|
+
provider: str, model: str, api_key: str, gateway_name: str
|
|
267
|
+
) -> "GopherAgent":
|
|
268
|
+
"""
|
|
269
|
+
Create a new GopherAgent scoped to a single MCP gateway by name.
|
|
270
|
+
|
|
271
|
+
Fetches server config from the Gopher API using the Bearer api key,
|
|
272
|
+
appending "?gatewayName={gateway_name}" so the response carries the
|
|
273
|
+
backing MCP servers for that gateway.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
provider: Provider name (e.g., "AnthropicProvider")
|
|
277
|
+
model: Model identifier accepted by the chosen provider
|
|
278
|
+
api_key: Gopher API key
|
|
279
|
+
gateway_name: MCP gateway name to scope the agent to
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
GopherAgent instance
|
|
283
|
+
"""
|
|
284
|
+
return GopherAgent._create_from_ffi(
|
|
285
|
+
lambda lib: lib.agent_create_by_gateway_name(
|
|
286
|
+
provider, model, api_key, gateway_name
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def create_with_url(provider: str, model: str, url: str) -> "GopherAgent":
|
|
292
|
+
"""
|
|
293
|
+
Create a new GopherAgent for a single MCP server reachable at a URL.
|
|
294
|
+
|
|
295
|
+
Skips the remote config fetch entirely: synthesises an http_sse
|
|
296
|
+
server entry around the URL and delegates to create_by_json on the
|
|
297
|
+
native side. Useful for local development or one-off endpoints where
|
|
298
|
+
the operator already knows the URL.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
provider: Provider name (e.g., "AnthropicProvider")
|
|
302
|
+
model: Model identifier accepted by the chosen provider
|
|
303
|
+
url: Full URL of the MCP server (e.g., "http://127.0.0.1:8080/mcp")
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
GopherAgent instance
|
|
307
|
+
"""
|
|
308
|
+
return GopherAgent._create_from_ffi(
|
|
309
|
+
lambda lib: lib.agent_create_by_url(provider, model, url)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def _create_from_ffi(
|
|
314
|
+
create_handle: Callable[[GopherOrchLibrary], Optional[GopherOrchHandle]],
|
|
315
|
+
) -> "GopherAgent":
|
|
316
|
+
"""
|
|
317
|
+
Shared handle-creation pump for factories that bypass GopherAgentConfig.
|
|
318
|
+
|
|
319
|
+
Ensures the native library is initialised, invokes the supplied FFI
|
|
320
|
+
callable, and translates a null handle return into AgentError using
|
|
321
|
+
the same last_error / clear_error contract as create().
|
|
322
|
+
"""
|
|
323
|
+
if not _initialized:
|
|
324
|
+
GopherAgent.init()
|
|
325
|
+
|
|
326
|
+
lib = GopherOrchLibrary.get_instance()
|
|
327
|
+
if lib is None:
|
|
328
|
+
raise AgentError("Native library not available")
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
handle = create_handle(lib)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
raise AgentError(f"Failed to create agent: {e}")
|
|
334
|
+
|
|
335
|
+
if handle is None:
|
|
336
|
+
error = lib.get_last_error_message()
|
|
337
|
+
lib.clear_error()
|
|
338
|
+
raise AgentError(error or "Failed to create agent")
|
|
339
|
+
|
|
340
|
+
return GopherAgent(handle)
|
|
341
|
+
|
|
186
342
|
def run(self, query: str, timeout_ms: int = 60000) -> str:
|
|
187
343
|
"""
|
|
188
344
|
Run a query against the agent.
|
|
@@ -9,10 +9,20 @@ from typing import Optional
|
|
|
9
9
|
|
|
10
10
|
class GopherAgentConfig:
|
|
11
11
|
"""
|
|
12
|
-
Immutable configuration for GopherAgent.
|
|
12
|
+
Immutable configuration for GopherAgent created via GopherAgent.create().
|
|
13
13
|
|
|
14
14
|
Use the builder() method to create configurations.
|
|
15
15
|
|
|
16
|
+
The builder accepts only the api_key / server_config XOR that maps to
|
|
17
|
+
the original gopher_orch_agent_create_by_api_key and
|
|
18
|
+
gopher_orch_agent_create_by_json C entry points. The five newer routing
|
|
19
|
+
factories (GopherAgent.create_with_server_id, create_with_server_name,
|
|
20
|
+
create_with_gateway_id, create_with_gateway_name, create_with_url) take
|
|
21
|
+
additional inputs (server / gateway identifier, or URL) that do not fit
|
|
22
|
+
that XOR shape and deliberately bypass this builder; they are exposed
|
|
23
|
+
as static methods on GopherAgent and dispatch into GopherOrchLibrary
|
|
24
|
+
directly via GopherAgent._create_from_ffi.
|
|
25
|
+
|
|
16
26
|
Example:
|
|
17
27
|
>>> config = (GopherAgentConfig.builder()
|
|
18
28
|
... .provider("AnthropicProvider")
|
|
@@ -134,6 +134,54 @@ class GopherOrchLibrary:
|
|
|
134
134
|
]
|
|
135
135
|
self._lib.gopher_orch_agent_create_by_api_key.restype = c_void_p
|
|
136
136
|
|
|
137
|
+
# Routing factories: scope the agent to a single MCP server or gateway
|
|
138
|
+
# selected by id / name, or to a known MCP URL. These C symbols landed
|
|
139
|
+
# in gopher-orch 0.1.23 -- wrapped in try / except so the SDK still
|
|
140
|
+
# loads against an older libgopher-orch, with the higher-level
|
|
141
|
+
# factories raising AgentError at call time if the symbol is missing.
|
|
142
|
+
try:
|
|
143
|
+
self._lib.gopher_orch_agent_create_by_server_id.argtypes = [
|
|
144
|
+
c_char_p,
|
|
145
|
+
c_char_p,
|
|
146
|
+
c_char_p,
|
|
147
|
+
c_char_p,
|
|
148
|
+
]
|
|
149
|
+
self._lib.gopher_orch_agent_create_by_server_id.restype = c_void_p
|
|
150
|
+
|
|
151
|
+
self._lib.gopher_orch_agent_create_by_server_name.argtypes = [
|
|
152
|
+
c_char_p,
|
|
153
|
+
c_char_p,
|
|
154
|
+
c_char_p,
|
|
155
|
+
c_char_p,
|
|
156
|
+
]
|
|
157
|
+
self._lib.gopher_orch_agent_create_by_server_name.restype = c_void_p
|
|
158
|
+
|
|
159
|
+
self._lib.gopher_orch_agent_create_by_gateway_id.argtypes = [
|
|
160
|
+
c_char_p,
|
|
161
|
+
c_char_p,
|
|
162
|
+
c_char_p,
|
|
163
|
+
c_char_p,
|
|
164
|
+
]
|
|
165
|
+
self._lib.gopher_orch_agent_create_by_gateway_id.restype = c_void_p
|
|
166
|
+
|
|
167
|
+
self._lib.gopher_orch_agent_create_by_gateway_name.argtypes = [
|
|
168
|
+
c_char_p,
|
|
169
|
+
c_char_p,
|
|
170
|
+
c_char_p,
|
|
171
|
+
c_char_p,
|
|
172
|
+
]
|
|
173
|
+
self._lib.gopher_orch_agent_create_by_gateway_name.restype = c_void_p
|
|
174
|
+
|
|
175
|
+
self._lib.gopher_orch_agent_create_by_url.argtypes = [
|
|
176
|
+
c_char_p,
|
|
177
|
+
c_char_p,
|
|
178
|
+
c_char_p,
|
|
179
|
+
]
|
|
180
|
+
self._lib.gopher_orch_agent_create_by_url.restype = c_void_p
|
|
181
|
+
except AttributeError:
|
|
182
|
+
# Older libgopher-orch builds (< 0.1.23) lack these symbols.
|
|
183
|
+
pass
|
|
184
|
+
|
|
137
185
|
self._lib.gopher_orch_agent_run.argtypes = [c_void_p, c_char_p, c_int64]
|
|
138
186
|
self._lib.gopher_orch_agent_run.restype = c_char_p
|
|
139
187
|
|
|
@@ -287,6 +335,106 @@ class GopherOrchLibrary:
|
|
|
287
335
|
api_key.encode("utf-8"),
|
|
288
336
|
)
|
|
289
337
|
|
|
338
|
+
def agent_create_by_server_id(
|
|
339
|
+
self, provider: str, model: str, api_key: str, server_id: str
|
|
340
|
+
) -> Optional[GopherOrchHandle]:
|
|
341
|
+
"""Create an agent scoped to a single MCP server by id.
|
|
342
|
+
|
|
343
|
+
The native side fetches server config from the Gopher API using the
|
|
344
|
+
Bearer api key, appending "?serverId={server_id}" so the response
|
|
345
|
+
carries only the matching MCP server entry.
|
|
346
|
+
"""
|
|
347
|
+
if not self._available or self._lib is None:
|
|
348
|
+
return None
|
|
349
|
+
fn = getattr(self._lib, "gopher_orch_agent_create_by_server_id", None)
|
|
350
|
+
if fn is None:
|
|
351
|
+
return None
|
|
352
|
+
return fn(
|
|
353
|
+
provider.encode("utf-8"),
|
|
354
|
+
model.encode("utf-8"),
|
|
355
|
+
api_key.encode("utf-8"),
|
|
356
|
+
server_id.encode("utf-8"),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def agent_create_by_server_name(
|
|
360
|
+
self, provider: str, model: str, api_key: str, server_name: str
|
|
361
|
+
) -> Optional[GopherOrchHandle]:
|
|
362
|
+
"""Create an agent scoped to a single MCP server by name.
|
|
363
|
+
|
|
364
|
+
Mirrors agent_create_by_server_id but routes via "?serverName=".
|
|
365
|
+
"""
|
|
366
|
+
if not self._available or self._lib is None:
|
|
367
|
+
return None
|
|
368
|
+
fn = getattr(self._lib, "gopher_orch_agent_create_by_server_name", None)
|
|
369
|
+
if fn is None:
|
|
370
|
+
return None
|
|
371
|
+
return fn(
|
|
372
|
+
provider.encode("utf-8"),
|
|
373
|
+
model.encode("utf-8"),
|
|
374
|
+
api_key.encode("utf-8"),
|
|
375
|
+
server_name.encode("utf-8"),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def agent_create_by_gateway_id(
|
|
379
|
+
self, provider: str, model: str, api_key: str, gateway_id: str
|
|
380
|
+
) -> Optional[GopherOrchHandle]:
|
|
381
|
+
"""Create an agent scoped to a single MCP gateway by id.
|
|
382
|
+
|
|
383
|
+
The native side appends "?gatewayId={gateway_id}" to the Gopher API
|
|
384
|
+
fetch so the response carries the backing MCP servers for that
|
|
385
|
+
gateway.
|
|
386
|
+
"""
|
|
387
|
+
if not self._available or self._lib is None:
|
|
388
|
+
return None
|
|
389
|
+
fn = getattr(self._lib, "gopher_orch_agent_create_by_gateway_id", None)
|
|
390
|
+
if fn is None:
|
|
391
|
+
return None
|
|
392
|
+
return fn(
|
|
393
|
+
provider.encode("utf-8"),
|
|
394
|
+
model.encode("utf-8"),
|
|
395
|
+
api_key.encode("utf-8"),
|
|
396
|
+
gateway_id.encode("utf-8"),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def agent_create_by_gateway_name(
|
|
400
|
+
self, provider: str, model: str, api_key: str, gateway_name: str
|
|
401
|
+
) -> Optional[GopherOrchHandle]:
|
|
402
|
+
"""Create an agent scoped to a single MCP gateway by name.
|
|
403
|
+
|
|
404
|
+
Mirrors agent_create_by_gateway_id but routes via "?gatewayName=".
|
|
405
|
+
"""
|
|
406
|
+
if not self._available or self._lib is None:
|
|
407
|
+
return None
|
|
408
|
+
fn = getattr(self._lib, "gopher_orch_agent_create_by_gateway_name", None)
|
|
409
|
+
if fn is None:
|
|
410
|
+
return None
|
|
411
|
+
return fn(
|
|
412
|
+
provider.encode("utf-8"),
|
|
413
|
+
model.encode("utf-8"),
|
|
414
|
+
api_key.encode("utf-8"),
|
|
415
|
+
gateway_name.encode("utf-8"),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def agent_create_by_url(
|
|
419
|
+
self, provider: str, model: str, url: str
|
|
420
|
+
) -> Optional[GopherOrchHandle]:
|
|
421
|
+
"""Create an agent for a single MCP server reachable at a URL.
|
|
422
|
+
|
|
423
|
+
Skips the remote config fetch: the native side synthesises an
|
|
424
|
+
http_sse server entry around the URL. Useful for local development
|
|
425
|
+
or one-off endpoints where the operator already knows the URL.
|
|
426
|
+
"""
|
|
427
|
+
if not self._available or self._lib is None:
|
|
428
|
+
return None
|
|
429
|
+
fn = getattr(self._lib, "gopher_orch_agent_create_by_url", None)
|
|
430
|
+
if fn is None:
|
|
431
|
+
return None
|
|
432
|
+
return fn(
|
|
433
|
+
provider.encode("utf-8"),
|
|
434
|
+
model.encode("utf-8"),
|
|
435
|
+
url.encode("utf-8"),
|
|
436
|
+
)
|
|
437
|
+
|
|
290
438
|
def agent_run(
|
|
291
439
|
self, agent: GopherOrchHandle, query: str, timeout_ms: int
|
|
292
440
|
) -> Optional[str]:
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/SOURCES.txt
RENAMED
|
@@ -28,6 +28,7 @@ gopher_mcp_python/ffi/auth/oauth_client.py
|
|
|
28
28
|
gopher_mcp_python/ffi/auth/session_manager.py
|
|
29
29
|
gopher_mcp_python/ffi/auth/types.py
|
|
30
30
|
gopher_mcp_python/ffi/auth/validation_options.py
|
|
31
|
+
tests/test_agent_create_by.py
|
|
31
32
|
tests/test_config.py
|
|
32
33
|
tests/test_ffi.py
|
|
33
34
|
tests/test_gopher_auth.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gopher-mcp-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.23"
|
|
8
8
|
description = "Python SDK for Gopher MCP - AI Agent orchestration framework with native performance"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contract tests for the five routing factories on GopherAgent.
|
|
3
|
+
|
|
4
|
+
Mirrors the failure-path test set in
|
|
5
|
+
gopher-orch/tests/gopher/orch/agent_create_by_test.cc which locks down
|
|
6
|
+
the nullptr-on-failure contract that the C FFI surfaces here as
|
|
7
|
+
AgentError. Happy-path coverage needs a stubbed HTTP listener capturing
|
|
8
|
+
the /v1/mcp-servers query string with the camelCase routing keys
|
|
9
|
+
(serverId / serverName / gatewayId / gatewayName); that infrastructure
|
|
10
|
+
is tracked separately, same as on the C++ side.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from gopher_mcp_python import AgentError, GopherAgent
|
|
16
|
+
from gopher_mcp_python.ffi import GopherOrchLibrary
|
|
17
|
+
|
|
18
|
+
PROVIDER = "AnthropicProvider"
|
|
19
|
+
MODEL = "test-model"
|
|
20
|
+
BAD_PROVIDER = "NotARealProvider"
|
|
21
|
+
URL = "http://127.0.0.1:8080/mcp"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
pytestmark = pytest.mark.skipif(
|
|
25
|
+
not GopherOrchLibrary.is_available(),
|
|
26
|
+
reason="Native library not available -- run ./build.sh first",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestRoutingFactoryContracts:
|
|
31
|
+
"""Failure-path contract for the five routing factories on GopherAgent."""
|
|
32
|
+
|
|
33
|
+
# ----------------------------------------------------------------
|
|
34
|
+
# Empty api key. fetch_mcp_servers throws on the native side; the
|
|
35
|
+
# factory must surface that as AgentError rather than returning a
|
|
36
|
+
# partially-constructed agent or a null handle leaking through.
|
|
37
|
+
# ----------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
def test_create_with_server_id_rejects_empty_api_key(self) -> None:
|
|
40
|
+
with pytest.raises(AgentError):
|
|
41
|
+
GopherAgent.create_with_server_id(PROVIDER, MODEL, "", "srv-1")
|
|
42
|
+
|
|
43
|
+
def test_create_with_server_name_rejects_empty_api_key(self) -> None:
|
|
44
|
+
with pytest.raises(AgentError):
|
|
45
|
+
GopherAgent.create_with_server_name(
|
|
46
|
+
PROVIDER, MODEL, "", "my-server"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def test_create_with_gateway_id_rejects_empty_api_key(self) -> None:
|
|
50
|
+
with pytest.raises(AgentError):
|
|
51
|
+
GopherAgent.create_with_gateway_id(PROVIDER, MODEL, "", "gw-1")
|
|
52
|
+
|
|
53
|
+
def test_create_with_gateway_name_rejects_empty_api_key(self) -> None:
|
|
54
|
+
with pytest.raises(AgentError):
|
|
55
|
+
GopherAgent.create_with_gateway_name(
|
|
56
|
+
PROVIDER, MODEL, "", "my-gateway"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# ----------------------------------------------------------------
|
|
60
|
+
# create_with_url rejects empty url before any FFI work happens.
|
|
61
|
+
# Mirrors the CreateByUrlRejectsEmptyUrl case in the C++ suite.
|
|
62
|
+
# ----------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def test_create_with_url_rejects_empty_url(self) -> None:
|
|
65
|
+
with pytest.raises(AgentError):
|
|
66
|
+
GopherAgent.create_with_url(PROVIDER, MODEL, "")
|
|
67
|
+
|
|
68
|
+
# ----------------------------------------------------------------
|
|
69
|
+
# Unknown provider. create_with_url synthesises a local http_sse
|
|
70
|
+
# config and reaches create_by_json on the native side, which
|
|
71
|
+
# rejects an unknown provider name. The factory must surface that
|
|
72
|
+
# as AgentError.
|
|
73
|
+
# ----------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
def test_create_with_url_rejects_unknown_provider(self) -> None:
|
|
76
|
+
with pytest.raises(AgentError):
|
|
77
|
+
GopherAgent.create_with_url(BAD_PROVIDER, MODEL, URL)
|
|
78
|
+
|
|
79
|
+
# ----------------------------------------------------------------
|
|
80
|
+
# AgentError surfaces a non-empty message so SDK consumers can log
|
|
81
|
+
# a meaningful diagnostic; the C side fills last_error() and the
|
|
82
|
+
# wrapper pump should propagate it through. A future change to the
|
|
83
|
+
# error pump that swallows the underlying C diagnostic gets caught
|
|
84
|
+
# here immediately.
|
|
85
|
+
# ----------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def test_create_with_server_id_surfaces_non_empty_message(self) -> None:
|
|
88
|
+
with pytest.raises(AgentError) as exc_info:
|
|
89
|
+
GopherAgent.create_with_server_id(PROVIDER, MODEL, "", "srv-1")
|
|
90
|
+
assert len(str(exc_info.value)) > 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/scope_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/__init__.py
RENAMED
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auth_client.py
RENAMED
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auto_refresh.py
RENAMED
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/config_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/oauth_client.py
RENAMED
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/session_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|