codeapi-client 0.4.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.
- codeapi/__init__.py +17 -0
- codeapi/client/__init__.py +37 -0
- codeapi/client/_async/__init__.py +357 -0
- codeapi/client/_async/_app.py +369 -0
- codeapi/client/_async/_cli.py +334 -0
- codeapi/client/_async/_code.py +211 -0
- codeapi/client/_async/_jobs.py +512 -0
- codeapi/client/_async/_mcp.py +445 -0
- codeapi/client/_async/_web.py +343 -0
- codeapi/client/_base.py +118 -0
- codeapi/client/_sync/__init__.py +347 -0
- codeapi/client/_sync/_app.py +367 -0
- codeapi/client/_sync/_cli.py +332 -0
- codeapi/client/_sync/_code.py +203 -0
- codeapi/client/_sync/_jobs.py +497 -0
- codeapi/client/_sync/_mcp.py +442 -0
- codeapi/client/_sync/_web.py +341 -0
- codeapi/client/_utils.py +61 -0
- codeapi/client/py.typed +0 -0
- codeapi/types/__init__.py +77 -0
- codeapi/types/_api.py +30 -0
- codeapi/types/_base.py +21 -0
- codeapi/types/_code.py +31 -0
- codeapi/types/_enums.py +150 -0
- codeapi/types/_env.py +65 -0
- codeapi/types/_exc.py +35 -0
- codeapi/types/_job.py +67 -0
- codeapi/types/_json.py +67 -0
- codeapi/types/_stream.py +36 -0
- codeapi/types/_swarm.py +85 -0
- codeapi/types/_time.py +46 -0
- codeapi/types/_zips.py +466 -0
- codeapi/types/py.typed +0 -0
- codeapi_client-0.4.1.dist-info/METADATA +14 -0
- codeapi_client-0.4.1.dist-info/RECORD +36 -0
- codeapi_client-0.4.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any, List, Literal
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from codeapi.types import (
|
|
10
|
+
CodeInfo,
|
|
11
|
+
CodeType,
|
|
12
|
+
CodeZip,
|
|
13
|
+
DataZip,
|
|
14
|
+
EnvVars,
|
|
15
|
+
Job,
|
|
16
|
+
JobStage,
|
|
17
|
+
JobStatus,
|
|
18
|
+
JobType,
|
|
19
|
+
JsonData,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from fastmcp.mcp_config import MCPConfig
|
|
24
|
+
|
|
25
|
+
from . import Client
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StoredMcpClient:
|
|
29
|
+
def __init__(self, client: Client):
|
|
30
|
+
self._client = client
|
|
31
|
+
|
|
32
|
+
def run(
|
|
33
|
+
self,
|
|
34
|
+
code_id: str,
|
|
35
|
+
mcp_module: str,
|
|
36
|
+
env_vars: EnvVars | dict | None = None,
|
|
37
|
+
transport: Literal["streamable-http", "http", "sse"] = "streamable-http",
|
|
38
|
+
data_zip: DataZip | None = None,
|
|
39
|
+
) -> Job:
|
|
40
|
+
"""Runs a stored MCP server.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
code_id (str): The code ID.
|
|
44
|
+
mcp_module (str): The name of the MCP server module.
|
|
45
|
+
env_vars (EnvVars | dict | None): Optional environment variables.
|
|
46
|
+
transport (Literal["streamable-http", "http", "sse"]): The transport protocol.
|
|
47
|
+
data_zip (DataZip | None): Optional data zip file.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Job: The created job.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
HTTPException: If the request fails.
|
|
54
|
+
"""
|
|
55
|
+
url = f"{self._client.base_url}/jobs/code/{code_id}/run/mcp"
|
|
56
|
+
|
|
57
|
+
files = self._client._prepare_files(data_zip=data_zip)
|
|
58
|
+
data: dict[str, Any] = {
|
|
59
|
+
"mcp_module": mcp_module,
|
|
60
|
+
"transport": transport,
|
|
61
|
+
}
|
|
62
|
+
if env_vars:
|
|
63
|
+
data["env_vars"] = EnvVars(env_vars).json_str
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
response = httpx.post(
|
|
67
|
+
url,
|
|
68
|
+
headers=self._client.api_key_header,
|
|
69
|
+
files=files if files else None,
|
|
70
|
+
data=data,
|
|
71
|
+
)
|
|
72
|
+
response.raise_for_status()
|
|
73
|
+
return Job(**response.json())
|
|
74
|
+
except httpx.HTTPStatusError as e:
|
|
75
|
+
raise self._client._get_http_exception(httpx_error=e)
|
|
76
|
+
|
|
77
|
+
def list_info(self) -> list[CodeInfo]:
|
|
78
|
+
"""List all stored MCP code.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
list[CodeInfo]: List of MCP code info.
|
|
82
|
+
"""
|
|
83
|
+
return self._client.code.list_info(code_type=CodeType.MCP)
|
|
84
|
+
|
|
85
|
+
def delete(self, code_id: str) -> str:
|
|
86
|
+
"""Delete stored MCP code.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
code_id (str): The code ID to delete.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: Deletion confirmation message.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If the code_id is not MCP code.
|
|
96
|
+
"""
|
|
97
|
+
# Verify this is actually MCP code
|
|
98
|
+
code_info = self._client.code.get_info(code_id)
|
|
99
|
+
if code_info.code_type != CodeType.MCP:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"Code '{code_id}' is {code_info.code_type}, not MCP code. "
|
|
102
|
+
"Cannot delete non-MCP code from MCP client."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return self._client.code.delete(code_id)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class McpConfigClient:
|
|
109
|
+
def __init__(self, client: Client):
|
|
110
|
+
self._client = client
|
|
111
|
+
|
|
112
|
+
def run(
|
|
113
|
+
self,
|
|
114
|
+
mcp_config: MCPConfig | Path | str | dict,
|
|
115
|
+
transport: Literal["streamable-http", "http", "sse"] = "streamable-http",
|
|
116
|
+
) -> Job:
|
|
117
|
+
"""Runs a MCP server from MCP configuration.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
mcp_config (MCPConfig | Path | str | dict): The MCP configuration. Can be:
|
|
121
|
+
- FastMCP MCPConfig object
|
|
122
|
+
- Path to a .json file containing MCP configuration
|
|
123
|
+
- JSON string containing MCP configuration
|
|
124
|
+
- Dictionary containing MCP configuration
|
|
125
|
+
transport (Literal["streamable-http", "http", "sse"]): The transport protocol.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Job: The created job.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
HTTPException: If the request fails.
|
|
132
|
+
"""
|
|
133
|
+
url = f"{self._client.base_url}/jobs/config/run/mcp"
|
|
134
|
+
|
|
135
|
+
if isinstance(mcp_config, (Path, str)):
|
|
136
|
+
if str(mcp_config).endswith(".json"):
|
|
137
|
+
config_path = Path(mcp_config)
|
|
138
|
+
with open(config_path, "r") as f:
|
|
139
|
+
config_data = json.load(f)
|
|
140
|
+
else:
|
|
141
|
+
config_data = json.loads(str(mcp_config))
|
|
142
|
+
elif isinstance(mcp_config, dict):
|
|
143
|
+
config_data = mcp_config
|
|
144
|
+
else:
|
|
145
|
+
config_data = mcp_config.model_dump()
|
|
146
|
+
|
|
147
|
+
data = {
|
|
148
|
+
"transport": transport,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
response = httpx.post(
|
|
153
|
+
url,
|
|
154
|
+
headers=self._client.api_key_header,
|
|
155
|
+
json=config_data,
|
|
156
|
+
params=data,
|
|
157
|
+
)
|
|
158
|
+
response.raise_for_status()
|
|
159
|
+
return Job(**response.json())
|
|
160
|
+
except httpx.HTTPStatusError as e:
|
|
161
|
+
raise self._client._get_http_exception(httpx_error=e)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class McpJobsClient:
|
|
165
|
+
def __init__(self, client):
|
|
166
|
+
self._client = client
|
|
167
|
+
|
|
168
|
+
def list(
|
|
169
|
+
self,
|
|
170
|
+
job_status: JobStatus | None = None,
|
|
171
|
+
job_stage: JobStage | None = None,
|
|
172
|
+
) -> List[Job]:
|
|
173
|
+
"""List MCP jobs.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
job_status (JobStatus | None): Filter by job status.
|
|
177
|
+
job_stage (JobStage | None): Filter by job stage.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
list[Job]: List of MCP jobs.
|
|
181
|
+
"""
|
|
182
|
+
return self._client.jobs.list(
|
|
183
|
+
job_type=JobType.RUN_MCP,
|
|
184
|
+
job_status=job_status,
|
|
185
|
+
job_stage=job_stage,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def get_latest(self) -> Job | None:
|
|
189
|
+
"""Get the most recent MCP job.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Job | None: The most recent MCP job, or None if no jobs exist.
|
|
193
|
+
"""
|
|
194
|
+
jobs = self.list()
|
|
195
|
+
return jobs[0] if jobs else None
|
|
196
|
+
|
|
197
|
+
def list_queued(self) -> List[Job]:
|
|
198
|
+
"""Get all queued MCP jobs.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List[Job]: List of queued MCP jobs.
|
|
202
|
+
"""
|
|
203
|
+
return self.list(job_status=JobStatus.QUEUED)
|
|
204
|
+
|
|
205
|
+
def list_scheduled(self) -> List[Job]:
|
|
206
|
+
"""Get all scheduled MCP jobs.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List[Job]: List of scheduled MCP jobs.
|
|
210
|
+
"""
|
|
211
|
+
return self.list(job_status=JobStatus.SCHEDULED)
|
|
212
|
+
|
|
213
|
+
def list_started(self) -> List[Job]:
|
|
214
|
+
"""Get all started MCP jobs.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
list[Job]: List of started MCP jobs.
|
|
218
|
+
"""
|
|
219
|
+
return self.list(job_status=JobStatus.STARTED)
|
|
220
|
+
|
|
221
|
+
def list_deferred(self) -> List[Job]:
|
|
222
|
+
"""Get all deferred MCP jobs.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List[Job]: List of deferred MCP jobs.
|
|
226
|
+
"""
|
|
227
|
+
return self.list(job_status=JobStatus.DEFERRED)
|
|
228
|
+
|
|
229
|
+
def list_canceled(self) -> List[Job]:
|
|
230
|
+
"""Get all canceled MCP jobs.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List[Job]: List of canceled MCP jobs.
|
|
234
|
+
"""
|
|
235
|
+
return self.list(job_status=JobStatus.CANCELED)
|
|
236
|
+
|
|
237
|
+
def list_stopped(self) -> List[Job]:
|
|
238
|
+
"""Get all stopped MCP jobs.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
List[Job]: List of stopped MCP jobs.
|
|
242
|
+
"""
|
|
243
|
+
return self.list(job_status=JobStatus.STOPPED)
|
|
244
|
+
|
|
245
|
+
def list_failed(self) -> List[Job]:
|
|
246
|
+
"""Get all failed MCP jobs.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
list[Job]: List of failed MCP jobs.
|
|
250
|
+
"""
|
|
251
|
+
return self.list(job_status=JobStatus.FAILED)
|
|
252
|
+
|
|
253
|
+
def list_finished(self) -> List[Job]:
|
|
254
|
+
"""Get all finished MCP jobs.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
list[Job]: List of finished MCP jobs.
|
|
258
|
+
"""
|
|
259
|
+
return self.list(job_status=JobStatus.FINISHED)
|
|
260
|
+
|
|
261
|
+
def list_timed_out(self) -> List[Job]:
|
|
262
|
+
"""Get all timed out MCP jobs.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List[Job]: List of timed out MCP jobs.
|
|
266
|
+
"""
|
|
267
|
+
return self.list(job_status=JobStatus.TIMEOUT)
|
|
268
|
+
|
|
269
|
+
def list_pre_running(self) -> List[Job]:
|
|
270
|
+
"""Get all pre-running MCP jobs.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
List[Job]: List of pre-running MCP jobs.
|
|
274
|
+
"""
|
|
275
|
+
return self.list(job_stage=JobStage.PRE_RUNNING)
|
|
276
|
+
|
|
277
|
+
def list_running(self) -> List[Job]:
|
|
278
|
+
"""Get all running MCP jobs.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
List[Job]: List of running MCP jobs.
|
|
282
|
+
"""
|
|
283
|
+
return self.list(job_stage=JobStage.RUNNING)
|
|
284
|
+
|
|
285
|
+
def list_post_running(self) -> List[Job]:
|
|
286
|
+
"""Get all post-running MCP jobs.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
List[Job]: List of post-running MCP jobs.
|
|
290
|
+
"""
|
|
291
|
+
return self.list(job_stage=JobStage.POST_RUNNING)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class McpClient:
|
|
295
|
+
def __init__(self, client: Client):
|
|
296
|
+
self._client = client
|
|
297
|
+
self.stored = StoredMcpClient(client)
|
|
298
|
+
self.config = McpConfigClient(client)
|
|
299
|
+
self.jobs = McpJobsClient(client)
|
|
300
|
+
|
|
301
|
+
def run(
|
|
302
|
+
self,
|
|
303
|
+
code_zip: CodeZip,
|
|
304
|
+
mcp_module: str,
|
|
305
|
+
env_vars: EnvVars | dict | None = None,
|
|
306
|
+
transport: Literal["streamable-http", "http", "sse"] = "streamable-http",
|
|
307
|
+
data_zip: DataZip | None = None,
|
|
308
|
+
) -> Job:
|
|
309
|
+
"""Runs a MCP server from code zip.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
code_zip (CodeZip): The code zip.
|
|
313
|
+
mcp_module (str): The name of the MCP server module.
|
|
314
|
+
env_vars (EnvVars | dict | None): Optional environment variables.
|
|
315
|
+
transport (Literal["streamable-http", "http", "sse"]): The transport protocol.
|
|
316
|
+
data_zip (DataZip | None): Optional data zip file.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Job: The created job.
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
HTTPException: If the request fails.
|
|
323
|
+
"""
|
|
324
|
+
url = f"{self._client.base_url}/jobs/code/run/mcp"
|
|
325
|
+
|
|
326
|
+
files = self._client._prepare_files(code_zip=code_zip, data_zip=data_zip)
|
|
327
|
+
data: dict[str, Any] = {
|
|
328
|
+
"mcp_module": mcp_module,
|
|
329
|
+
"transport": transport,
|
|
330
|
+
}
|
|
331
|
+
if env_vars:
|
|
332
|
+
data["env_vars"] = EnvVars(env_vars).json_str
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
response = httpx.post(
|
|
336
|
+
url,
|
|
337
|
+
headers=self._client.api_key_header,
|
|
338
|
+
files=files,
|
|
339
|
+
data=data,
|
|
340
|
+
)
|
|
341
|
+
response.raise_for_status()
|
|
342
|
+
return Job(**response.json())
|
|
343
|
+
except httpx.HTTPStatusError as e:
|
|
344
|
+
raise self._client._get_http_exception(httpx_error=e)
|
|
345
|
+
|
|
346
|
+
def is_healthy(self, job_id: str) -> bool:
|
|
347
|
+
"""Checks whether launched MCP server is healthy.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
job_id (str): The ID of the MCP launch job.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
bool: True if MCP is healthy else False.
|
|
354
|
+
|
|
355
|
+
Raises:
|
|
356
|
+
HTTPException: If the request fails.
|
|
357
|
+
"""
|
|
358
|
+
return self._client.jobs.is_healthy(job_id=job_id)
|
|
359
|
+
|
|
360
|
+
def await_healthy(self, job_id: str, timeout: float | None = None) -> Job:
|
|
361
|
+
"""Waits for a MCP server to become healthy.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
job_id (str): The ID of the MCP run job.
|
|
365
|
+
timeout (float | None): Maximum time to wait in seconds. If None, waits indefinitely.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Job: The job object of the MCP run job.
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
HTTPException: If the request fails.
|
|
372
|
+
APIException: If the job enters stage POST_RUNNING unexpectedly.
|
|
373
|
+
TimeoutError: If the timeout is exceeded.
|
|
374
|
+
"""
|
|
375
|
+
return self._client.jobs.await_healthy(job_id=job_id, timeout=timeout)
|
|
376
|
+
|
|
377
|
+
def get_url(
|
|
378
|
+
self, job_id: str, transport: Literal["streamable-http", "http", "sse"] = "http"
|
|
379
|
+
) -> str:
|
|
380
|
+
"""Gets the URL for a MCP server.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
job_id (str): The job ID.
|
|
384
|
+
transport (Literal["streamable-http", "http", "sse"]): The transport protocol.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
str: The MCP URL.
|
|
388
|
+
"""
|
|
389
|
+
mcp_path = "sse" if transport == "sse" else "mcp"
|
|
390
|
+
return f"{self._client.get_proxy_url(job_id)}/{mcp_path}"
|
|
391
|
+
|
|
392
|
+
def upload(
|
|
393
|
+
self,
|
|
394
|
+
code_zip: CodeZip,
|
|
395
|
+
code_name: str,
|
|
396
|
+
metadata: JsonData | dict | None = None,
|
|
397
|
+
) -> str:
|
|
398
|
+
"""Upload MCP code.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
code_zip (CodeZip): The code zip.
|
|
402
|
+
code_name (str): The name of the code.
|
|
403
|
+
metadata (JsonData | dict | None): The JSON metadata of the code.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
str: The code ID.
|
|
407
|
+
"""
|
|
408
|
+
return self._client.code.upload(
|
|
409
|
+
code_zip=code_zip,
|
|
410
|
+
code_name=code_name,
|
|
411
|
+
code_type=CodeType.MCP,
|
|
412
|
+
metadata=metadata,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def ingest(
|
|
416
|
+
self,
|
|
417
|
+
code_zip: CodeZip,
|
|
418
|
+
code_name: str,
|
|
419
|
+
metadata: JsonData | dict | None = None,
|
|
420
|
+
build_pexenv: bool = False,
|
|
421
|
+
pexenv_python: str | None = None,
|
|
422
|
+
) -> Job:
|
|
423
|
+
"""Ingest MCP code.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
code_zip (CodeZip): The code zip.
|
|
427
|
+
code_name (str): The name of the code.
|
|
428
|
+
metadata (JsonData | dict | None): The JSON metadata of the code.
|
|
429
|
+
build_pexenv (bool): Whether to build the pex venv.
|
|
430
|
+
pexenv_python: (str | None): Python interpreter for the pex venv.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Job: The code ingestion job.
|
|
434
|
+
"""
|
|
435
|
+
return self._client.code.ingest(
|
|
436
|
+
code_zip=code_zip,
|
|
437
|
+
code_name=code_name,
|
|
438
|
+
code_type=CodeType.MCP,
|
|
439
|
+
metadata=metadata,
|
|
440
|
+
build_pexenv=build_pexenv,
|
|
441
|
+
pexenv_python=pexenv_python,
|
|
442
|
+
)
|