glaip-sdk 0.6.11__py3-none-any.whl → 0.6.14__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.
- glaip_sdk/__init__.py +42 -5
- {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
- glaip_sdk-0.6.14.dist-info/RECORD +12 -0
- {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
- glaip_sdk/agents/__init__.py +0 -27
- glaip_sdk/agents/base.py +0 -1191
- glaip_sdk/cli/__init__.py +0 -9
- glaip_sdk/cli/account_store.py +0 -540
- glaip_sdk/cli/agent_config.py +0 -78
- glaip_sdk/cli/auth.py +0 -699
- glaip_sdk/cli/commands/__init__.py +0 -5
- glaip_sdk/cli/commands/accounts.py +0 -746
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/common_config.py +0 -101
- glaip_sdk/cli/commands/configure.py +0 -896
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/models.py +0 -69
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/commands/transcripts.py +0 -755
- glaip_sdk/cli/commands/update.py +0 -61
- glaip_sdk/cli/config.py +0 -95
- glaip_sdk/cli/constants.py +0 -38
- glaip_sdk/cli/context.py +0 -150
- glaip_sdk/cli/core/__init__.py +0 -79
- glaip_sdk/cli/core/context.py +0 -124
- glaip_sdk/cli/core/output.py +0 -846
- glaip_sdk/cli/core/prompting.py +0 -649
- glaip_sdk/cli/core/rendering.py +0 -187
- glaip_sdk/cli/display.py +0 -355
- glaip_sdk/cli/hints.py +0 -57
- glaip_sdk/cli/io.py +0 -112
- glaip_sdk/cli/main.py +0 -604
- glaip_sdk/cli/masking.py +0 -136
- glaip_sdk/cli/mcp_validators.py +0 -287
- glaip_sdk/cli/pager.py +0 -266
- glaip_sdk/cli/parsers/__init__.py +0 -7
- glaip_sdk/cli/parsers/json_input.py +0 -177
- glaip_sdk/cli/resolution.py +0 -67
- glaip_sdk/cli/rich_helpers.py +0 -27
- glaip_sdk/cli/slash/__init__.py +0 -15
- glaip_sdk/cli/slash/accounts_controller.py +0 -578
- glaip_sdk/cli/slash/accounts_shared.py +0 -75
- glaip_sdk/cli/slash/agent_session.py +0 -285
- glaip_sdk/cli/slash/prompt.py +0 -256
- glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
- glaip_sdk/cli/slash/session.py +0 -1708
- glaip_sdk/cli/slash/tui/__init__.py +0 -9
- glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
- glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
- glaip_sdk/cli/slash/tui/loading.py +0 -58
- glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
- glaip_sdk/cli/transcript/__init__.py +0 -31
- glaip_sdk/cli/transcript/cache.py +0 -536
- glaip_sdk/cli/transcript/capture.py +0 -329
- glaip_sdk/cli/transcript/export.py +0 -38
- glaip_sdk/cli/transcript/history.py +0 -815
- glaip_sdk/cli/transcript/launcher.py +0 -77
- glaip_sdk/cli/transcript/viewer.py +0 -374
- glaip_sdk/cli/update_notifier.py +0 -290
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk/cli/validators.py +0 -238
- glaip_sdk/client/__init__.py +0 -11
- glaip_sdk/client/_agent_payloads.py +0 -520
- glaip_sdk/client/agent_runs.py +0 -147
- glaip_sdk/client/agents.py +0 -1335
- glaip_sdk/client/base.py +0 -502
- glaip_sdk/client/main.py +0 -249
- glaip_sdk/client/mcps.py +0 -370
- glaip_sdk/client/run_rendering.py +0 -700
- glaip_sdk/client/shared.py +0 -21
- glaip_sdk/client/tools.py +0 -661
- glaip_sdk/client/validators.py +0 -198
- glaip_sdk/config/constants.py +0 -52
- glaip_sdk/mcps/__init__.py +0 -21
- glaip_sdk/mcps/base.py +0 -345
- glaip_sdk/models/__init__.py +0 -90
- glaip_sdk/models/agent.py +0 -47
- glaip_sdk/models/agent_runs.py +0 -116
- glaip_sdk/models/common.py +0 -42
- glaip_sdk/models/mcp.py +0 -33
- glaip_sdk/models/tool.py +0 -33
- glaip_sdk/payload_schemas/__init__.py +0 -7
- glaip_sdk/payload_schemas/agent.py +0 -85
- glaip_sdk/registry/__init__.py +0 -55
- glaip_sdk/registry/agent.py +0 -164
- glaip_sdk/registry/base.py +0 -139
- glaip_sdk/registry/mcp.py +0 -253
- glaip_sdk/registry/tool.py +0 -232
- glaip_sdk/runner/__init__.py +0 -59
- glaip_sdk/runner/base.py +0 -84
- glaip_sdk/runner/deps.py +0 -115
- glaip_sdk/runner/langgraph.py +0 -782
- glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
- glaip_sdk/runner/tool_adapter/__init__.py +0 -18
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
- glaip_sdk/tools/__init__.py +0 -22
- glaip_sdk/tools/base.py +0 -435
- glaip_sdk/utils/__init__.py +0 -86
- glaip_sdk/utils/a2a/__init__.py +0 -34
- glaip_sdk/utils/a2a/event_processor.py +0 -188
- glaip_sdk/utils/agent_config.py +0 -194
- glaip_sdk/utils/bundler.py +0 -267
- glaip_sdk/utils/client.py +0 -111
- glaip_sdk/utils/client_utils.py +0 -486
- glaip_sdk/utils/datetime_helpers.py +0 -58
- glaip_sdk/utils/discovery.py +0 -78
- glaip_sdk/utils/display.py +0 -135
- glaip_sdk/utils/export.py +0 -143
- glaip_sdk/utils/general.py +0 -61
- glaip_sdk/utils/import_export.py +0 -168
- glaip_sdk/utils/import_resolver.py +0 -492
- glaip_sdk/utils/instructions.py +0 -101
- glaip_sdk/utils/rendering/__init__.py +0 -115
- glaip_sdk/utils/rendering/formatting.py +0 -264
- glaip_sdk/utils/rendering/layout/__init__.py +0 -64
- glaip_sdk/utils/rendering/layout/panels.py +0 -156
- glaip_sdk/utils/rendering/layout/progress.py +0 -202
- glaip_sdk/utils/rendering/layout/summary.py +0 -74
- glaip_sdk/utils/rendering/layout/transcript.py +0 -606
- glaip_sdk/utils/rendering/models.py +0 -85
- glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
- glaip_sdk/utils/rendering/renderer/base.py +0 -1024
- glaip_sdk/utils/rendering/renderer/config.py +0 -27
- glaip_sdk/utils/rendering/renderer/console.py +0 -55
- glaip_sdk/utils/rendering/renderer/debug.py +0 -178
- glaip_sdk/utils/rendering/renderer/factory.py +0 -138
- glaip_sdk/utils/rendering/renderer/stream.py +0 -202
- glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
- glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
- glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
- glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
- glaip_sdk/utils/rendering/state.py +0 -204
- glaip_sdk/utils/rendering/step_tree_state.py +0 -100
- glaip_sdk/utils/rendering/steps/__init__.py +0 -34
- glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
- glaip_sdk/utils/rendering/steps/format.py +0 -176
- glaip_sdk/utils/rendering/steps/manager.py +0 -387
- glaip_sdk/utils/rendering/timing.py +0 -36
- glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
- glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
- glaip_sdk/utils/resource_refs.py +0 -195
- glaip_sdk/utils/run_renderer.py +0 -41
- glaip_sdk/utils/runtime_config.py +0 -425
- glaip_sdk/utils/serialization.py +0 -424
- glaip_sdk/utils/sync.py +0 -142
- glaip_sdk/utils/tool_detection.py +0 -33
- glaip_sdk/utils/validation.py +0 -264
- glaip_sdk-0.6.11.dist-info/RECORD +0 -159
- glaip_sdk-0.6.11.dist-info/entry_points.txt +0 -3
glaip_sdk/client/main.py
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Main client for AIP SDK.
|
|
3
|
-
|
|
4
|
-
Authors:
|
|
5
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
-
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from typing import TYPE_CHECKING, Any
|
|
12
|
-
|
|
13
|
-
from glaip_sdk.client.agents import AgentClient
|
|
14
|
-
from glaip_sdk.client.base import BaseClient
|
|
15
|
-
from glaip_sdk.client.mcps import MCPClient
|
|
16
|
-
from glaip_sdk.client.shared import build_shared_config
|
|
17
|
-
from glaip_sdk.client.tools import ToolClient
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING: # pragma: no cover
|
|
20
|
-
from glaip_sdk.agents import Agent
|
|
21
|
-
from glaip_sdk.client._agent_payloads import AgentListResult
|
|
22
|
-
from glaip_sdk.mcps import MCP
|
|
23
|
-
from glaip_sdk.tools import Tool
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Client(BaseClient):
|
|
27
|
-
"""Main client that composes all specialized clients and shares one HTTP session."""
|
|
28
|
-
|
|
29
|
-
def __init__(self, **kwargs):
|
|
30
|
-
"""Initialize the main client.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
**kwargs: Client configuration arguments (api_url, api_key, timeout, etc.)
|
|
34
|
-
"""
|
|
35
|
-
super().__init__(**kwargs)
|
|
36
|
-
# Share the single httpx.Client + config with sub-clients
|
|
37
|
-
shared_config = build_shared_config(self)
|
|
38
|
-
self.agents = AgentClient(**shared_config)
|
|
39
|
-
self.tools = ToolClient(**shared_config)
|
|
40
|
-
self.mcps = MCPClient(**shared_config)
|
|
41
|
-
|
|
42
|
-
# ---- Core API Methods (Public Interface) ----
|
|
43
|
-
|
|
44
|
-
# Agents
|
|
45
|
-
def create_agent(self, **kwargs) -> Agent:
|
|
46
|
-
"""Create a new agent."""
|
|
47
|
-
return self.agents.create_agent(**kwargs)
|
|
48
|
-
|
|
49
|
-
def create_agent_from_file(self, *args, **kwargs) -> Agent:
|
|
50
|
-
"""Create a new agent from a JSON or YAML configuration file."""
|
|
51
|
-
return self.agents.create_agent_from_file(*args, **kwargs)
|
|
52
|
-
|
|
53
|
-
def list_agents(
|
|
54
|
-
self,
|
|
55
|
-
agent_type: str | None = None,
|
|
56
|
-
framework: str | None = None,
|
|
57
|
-
name: str | None = None,
|
|
58
|
-
version: str | None = None,
|
|
59
|
-
sync_langflow_agents: bool = False,
|
|
60
|
-
) -> AgentListResult:
|
|
61
|
-
"""List agents with optional filtering.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
agent_type: Filter by agent type (config, code, a2a)
|
|
65
|
-
framework: Filter by framework (langchain, langgraph, google_adk)
|
|
66
|
-
name: Filter by partial name match (case-insensitive)
|
|
67
|
-
version: Filter by exact version match
|
|
68
|
-
sync_langflow_agents: Sync with LangFlow server before listing (only applies when agent_type=langflow)
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
AgentListResult with agents and pagination metadata. Supports iteration and indexing.
|
|
72
|
-
"""
|
|
73
|
-
return self.agents.list_agents(
|
|
74
|
-
agent_type=agent_type,
|
|
75
|
-
framework=framework,
|
|
76
|
-
name=name,
|
|
77
|
-
version=version,
|
|
78
|
-
sync_langflow_agents=sync_langflow_agents,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
def get_agent_by_id(self, agent_id: str) -> Agent | None:
|
|
82
|
-
"""Get agent by ID."""
|
|
83
|
-
return self.agents.get_agent_by_id(agent_id)
|
|
84
|
-
|
|
85
|
-
def get_agent(self, agent_id: str) -> Agent | None:
|
|
86
|
-
"""Get agent by ID (alias for get_agent_by_id)."""
|
|
87
|
-
return self.get_agent_by_id(agent_id)
|
|
88
|
-
|
|
89
|
-
def find_agents(self, name: str | None = None) -> list[Agent]:
|
|
90
|
-
"""Find agents by name."""
|
|
91
|
-
return self.agents.find_agents(name)
|
|
92
|
-
|
|
93
|
-
def update_agent(self, agent_id: str, **kwargs) -> Agent:
|
|
94
|
-
"""Update an existing agent."""
|
|
95
|
-
return self.agents.update_agent(agent_id, **kwargs)
|
|
96
|
-
|
|
97
|
-
def update_agent_from_file(self, agent_id: str, *args, **kwargs) -> Agent:
|
|
98
|
-
"""Update an existing agent using a JSON or YAML configuration file."""
|
|
99
|
-
return self.agents.update_agent_from_file(agent_id, *args, **kwargs)
|
|
100
|
-
|
|
101
|
-
def delete_agent(self, agent_id: str) -> bool:
|
|
102
|
-
"""Delete an agent."""
|
|
103
|
-
return self.agents.delete_agent(agent_id)
|
|
104
|
-
|
|
105
|
-
def run_agent(self, agent_id: str, message: str, **kwargs) -> str:
|
|
106
|
-
"""Run an agent with a message."""
|
|
107
|
-
return self.agents.run_agent(agent_id, message, **kwargs)
|
|
108
|
-
|
|
109
|
-
def sync_langflow_agents(
|
|
110
|
-
self,
|
|
111
|
-
base_url: str | None = None,
|
|
112
|
-
api_key: str | None = None,
|
|
113
|
-
) -> dict[str, Any]:
|
|
114
|
-
"""Sync LangFlow agents by fetching flows from the LangFlow server.
|
|
115
|
-
|
|
116
|
-
This method synchronizes agents with LangFlow flows. It fetches all flows
|
|
117
|
-
from the configured LangFlow server and creates/updates corresponding agents.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
base_url: Custom LangFlow server base URL. If not provided, uses LANGFLOW_BASE_URL env var.
|
|
121
|
-
api_key: Custom LangFlow API key. If not provided, uses LANGFLOW_API_KEY env var.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
Response containing sync results and statistics
|
|
125
|
-
"""
|
|
126
|
-
return self.agents.sync_langflow_agents(base_url=base_url, api_key=api_key)
|
|
127
|
-
|
|
128
|
-
# Tools
|
|
129
|
-
def create_tool(self, **kwargs) -> Tool:
|
|
130
|
-
"""Create a new tool."""
|
|
131
|
-
return self.tools.create_tool(**kwargs)
|
|
132
|
-
|
|
133
|
-
def create_tool_from_code(self, **kwargs) -> Tool:
|
|
134
|
-
"""Create a new tool from code."""
|
|
135
|
-
return self.tools.create_tool_from_code(**kwargs)
|
|
136
|
-
|
|
137
|
-
def list_tools(self, tool_type: str | None = None) -> list[Tool]:
|
|
138
|
-
"""List tools with optional type filtering."""
|
|
139
|
-
return self.tools.list_tools(tool_type=tool_type)
|
|
140
|
-
|
|
141
|
-
def get_tool_by_id(self, tool_id: str) -> Tool | None:
|
|
142
|
-
"""Get tool by ID."""
|
|
143
|
-
return self.tools.get_tool_by_id(tool_id)
|
|
144
|
-
|
|
145
|
-
def get_tool(self, tool_id: str) -> Tool | None:
|
|
146
|
-
"""Backward-compatible alias for get_tool_by_id."""
|
|
147
|
-
return self.get_tool_by_id(tool_id)
|
|
148
|
-
|
|
149
|
-
def find_tools(self, name: str) -> list[Tool]:
|
|
150
|
-
"""Find tools by name."""
|
|
151
|
-
return self.tools.find_tools(name)
|
|
152
|
-
|
|
153
|
-
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
154
|
-
"""Update an existing tool."""
|
|
155
|
-
return self.tools.update_tool(tool_id, **kwargs)
|
|
156
|
-
|
|
157
|
-
def delete_tool(self, tool_id: str) -> bool:
|
|
158
|
-
"""Delete a tool."""
|
|
159
|
-
return self.tools.delete_tool(tool_id)
|
|
160
|
-
|
|
161
|
-
def get_tool_script(self, tool_id: str) -> str:
|
|
162
|
-
"""Get tool script content."""
|
|
163
|
-
return self.tools.get_tool_script(tool_id)
|
|
164
|
-
|
|
165
|
-
def update_tool_via_file(self, tool_id: str, file_path: str, **kwargs) -> Tool:
|
|
166
|
-
"""Update tool via file."""
|
|
167
|
-
return self.tools.update_tool_via_file(tool_id, file_path, **kwargs)
|
|
168
|
-
|
|
169
|
-
# MCPs
|
|
170
|
-
def create_mcp(self, **kwargs) -> MCP:
|
|
171
|
-
"""Create a new MCP."""
|
|
172
|
-
return self.mcps.create_mcp(**kwargs)
|
|
173
|
-
|
|
174
|
-
def list_mcps(self) -> list[MCP]:
|
|
175
|
-
"""List all MCPs."""
|
|
176
|
-
return self.mcps.list_mcps()
|
|
177
|
-
|
|
178
|
-
def get_mcp_by_id(self, mcp_id: str) -> MCP | None:
|
|
179
|
-
"""Get MCP by ID."""
|
|
180
|
-
return self.mcps.get_mcp_by_id(mcp_id)
|
|
181
|
-
|
|
182
|
-
def get_mcp(self, mcp_id: str) -> MCP | None:
|
|
183
|
-
"""Backward-compatible alias for get_mcp_by_id."""
|
|
184
|
-
return self.get_mcp_by_id(mcp_id)
|
|
185
|
-
|
|
186
|
-
def find_mcps(self, name: str) -> list[MCP]:
|
|
187
|
-
"""Find MCPs by name."""
|
|
188
|
-
return self.mcps.find_mcps(name)
|
|
189
|
-
|
|
190
|
-
def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
|
|
191
|
-
"""Update an existing MCP."""
|
|
192
|
-
return self.mcps.update_mcp(mcp_id, **kwargs)
|
|
193
|
-
|
|
194
|
-
def delete_mcp(self, mcp_id: str) -> bool:
|
|
195
|
-
"""Delete an MCP."""
|
|
196
|
-
return self.mcps.delete_mcp(mcp_id)
|
|
197
|
-
|
|
198
|
-
def test_mcp_connection(self, config: dict) -> dict:
|
|
199
|
-
"""Test MCP connection."""
|
|
200
|
-
return self.mcps.test_mcp_connection(config)
|
|
201
|
-
|
|
202
|
-
def test_mcp_connection_from_config(self, config: dict) -> dict:
|
|
203
|
-
"""Test MCP connection from config."""
|
|
204
|
-
return self.mcps.test_mcp_connection_from_config(config)
|
|
205
|
-
|
|
206
|
-
def get_mcp_tools_from_config(self, config: dict) -> list[dict]:
|
|
207
|
-
"""Get MCP tools from config."""
|
|
208
|
-
return self.mcps.get_mcp_tools_from_config(config)
|
|
209
|
-
|
|
210
|
-
# Language Models
|
|
211
|
-
def list_language_models(self) -> list[dict]:
|
|
212
|
-
"""List available language models."""
|
|
213
|
-
data = self._request("GET", "/language-models")
|
|
214
|
-
return data or []
|
|
215
|
-
|
|
216
|
-
# ---- Timeout propagation ----
|
|
217
|
-
@property
|
|
218
|
-
def timeout(self) -> float: # type: ignore[override]
|
|
219
|
-
"""Get the client timeout value."""
|
|
220
|
-
return super().timeout
|
|
221
|
-
|
|
222
|
-
@timeout.setter
|
|
223
|
-
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
224
|
-
"""Set the client timeout and propagate to sub-clients.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
value: Timeout value in seconds.
|
|
228
|
-
"""
|
|
229
|
-
# Rebuild the root http client
|
|
230
|
-
BaseClient.timeout.fset(self, value) # call parent setter
|
|
231
|
-
# Propagate the new session to sub-clients so they don't hold a closed client
|
|
232
|
-
try:
|
|
233
|
-
if hasattr(self, "agents"):
|
|
234
|
-
self.agents.http_client = self.http_client
|
|
235
|
-
if hasattr(self, "tools"):
|
|
236
|
-
self.tools.http_client = self.http_client
|
|
237
|
-
if hasattr(self, "mcps"):
|
|
238
|
-
self.mcps.http_client = self.http_client
|
|
239
|
-
except Exception:
|
|
240
|
-
pass
|
|
241
|
-
|
|
242
|
-
# ---- Health Check ----
|
|
243
|
-
def ping(self) -> bool:
|
|
244
|
-
"""Check if the API is reachable."""
|
|
245
|
-
try:
|
|
246
|
-
self._request("GET", "/health-check")
|
|
247
|
-
return True
|
|
248
|
-
except Exception:
|
|
249
|
-
return False
|
glaip_sdk/client/mcps.py
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""MCP client for AIP SDK.
|
|
3
|
-
|
|
4
|
-
Authors:
|
|
5
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
-
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from glaip_sdk.client.base import BaseClient
|
|
13
|
-
from glaip_sdk.config.constants import DEFAULT_MCP_TRANSPORT, DEFAULT_MCP_TYPE
|
|
14
|
-
from glaip_sdk.mcps import MCP
|
|
15
|
-
from glaip_sdk.models import MCPResponse
|
|
16
|
-
from glaip_sdk.utils.client_utils import (
|
|
17
|
-
add_kwargs_to_payload,
|
|
18
|
-
create_model_instances,
|
|
19
|
-
find_by_name,
|
|
20
|
-
)
|
|
21
|
-
from glaip_sdk.utils.resource_refs import is_uuid
|
|
22
|
-
|
|
23
|
-
# API endpoints
|
|
24
|
-
MCPS_ENDPOINT = "/mcps/"
|
|
25
|
-
MCPS_CONNECT_ENDPOINT = "/mcps/connect"
|
|
26
|
-
MCPS_CONNECT_TOOLS_ENDPOINT = "/mcps/connect/tools"
|
|
27
|
-
|
|
28
|
-
# Set up module-level logger
|
|
29
|
-
logger = logging.getLogger("glaip_sdk.mcps")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class MCPClient(BaseClient):
|
|
33
|
-
"""Client for MCP operations."""
|
|
34
|
-
|
|
35
|
-
def __init__(self, *, parent_client: BaseClient | None = None, **kwargs):
|
|
36
|
-
"""Initialize the MCP client.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
parent_client: Parent client to adopt session/config from
|
|
40
|
-
**kwargs: Additional arguments for standalone initialization
|
|
41
|
-
"""
|
|
42
|
-
super().__init__(parent_client=parent_client, **kwargs)
|
|
43
|
-
|
|
44
|
-
def list_mcps(self) -> list[MCP]:
|
|
45
|
-
"""List all MCPs."""
|
|
46
|
-
data = self._request("GET", MCPS_ENDPOINT)
|
|
47
|
-
return create_model_instances(data, MCP, self)
|
|
48
|
-
|
|
49
|
-
def get_mcp_by_id(self, mcp_id: str) -> MCP:
|
|
50
|
-
"""Get MCP by ID."""
|
|
51
|
-
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
52
|
-
response = MCPResponse(**data)
|
|
53
|
-
return MCP.from_response(response, client=self)
|
|
54
|
-
|
|
55
|
-
def find_mcps(self, name: str | None = None) -> list[MCP]:
|
|
56
|
-
"""Find MCPs by name."""
|
|
57
|
-
# Backend doesn't support name query parameter, so we fetch all and filter client-side
|
|
58
|
-
data = self._request("GET", MCPS_ENDPOINT)
|
|
59
|
-
mcps = create_model_instances(data, MCP, self)
|
|
60
|
-
return find_by_name(mcps, name, case_sensitive=False)
|
|
61
|
-
|
|
62
|
-
def create_mcp(
|
|
63
|
-
self,
|
|
64
|
-
name: str,
|
|
65
|
-
description: str | None = None,
|
|
66
|
-
config: dict[str, Any] | None = None,
|
|
67
|
-
**kwargs,
|
|
68
|
-
) -> MCP:
|
|
69
|
-
"""Create a new MCP."""
|
|
70
|
-
# Use the helper method to build a properly structured payload
|
|
71
|
-
payload = self._build_create_payload(
|
|
72
|
-
name=name,
|
|
73
|
-
description=description,
|
|
74
|
-
config=config,
|
|
75
|
-
**kwargs,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
# Create the MCP and fetch full details
|
|
79
|
-
full_mcp_data = self._post_then_fetch(
|
|
80
|
-
id_key="id",
|
|
81
|
-
post_endpoint=MCPS_ENDPOINT,
|
|
82
|
-
get_endpoint_fmt=f"{MCPS_ENDPOINT}{{id}}",
|
|
83
|
-
json=payload,
|
|
84
|
-
)
|
|
85
|
-
response = MCPResponse(**full_mcp_data)
|
|
86
|
-
return MCP.from_response(response, client=self)
|
|
87
|
-
|
|
88
|
-
def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
|
|
89
|
-
"""Update an existing MCP.
|
|
90
|
-
|
|
91
|
-
Automatically chooses between PUT (full update) and PATCH (partial update)
|
|
92
|
-
based on the provided fields:
|
|
93
|
-
- Uses PUT if name, config, and transport are all provided (full update)
|
|
94
|
-
- Uses PATCH otherwise (partial update)
|
|
95
|
-
"""
|
|
96
|
-
# Check if all required fields for full update are provided
|
|
97
|
-
required_fields = {"name", "config", "transport"}
|
|
98
|
-
provided_fields = set(kwargs.keys())
|
|
99
|
-
|
|
100
|
-
if required_fields.issubset(provided_fields):
|
|
101
|
-
# All required fields provided - use full update (PUT)
|
|
102
|
-
method = "PUT"
|
|
103
|
-
else:
|
|
104
|
-
# Partial update - use PATCH
|
|
105
|
-
method = "PATCH"
|
|
106
|
-
|
|
107
|
-
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id}", json=kwargs)
|
|
108
|
-
response = MCPResponse(**data)
|
|
109
|
-
return MCP.from_response(response, client=self)
|
|
110
|
-
|
|
111
|
-
def delete_mcp(self, mcp_id: str) -> None:
|
|
112
|
-
"""Delete an MCP."""
|
|
113
|
-
self._request("DELETE", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
114
|
-
|
|
115
|
-
def upsert_mcp(
|
|
116
|
-
self,
|
|
117
|
-
identifier: str | MCP,
|
|
118
|
-
description: str | None = None,
|
|
119
|
-
config: dict[str, Any] | None = None,
|
|
120
|
-
**kwargs,
|
|
121
|
-
) -> MCP:
|
|
122
|
-
"""Create or update an MCP by instance, ID, or name.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
identifier: MCP instance, ID (UUID string), or name
|
|
126
|
-
description: MCP description
|
|
127
|
-
config: MCP configuration dictionary
|
|
128
|
-
**kwargs: Additional parameters (transport, metadata, etc.)
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
The created or updated MCP.
|
|
132
|
-
|
|
133
|
-
Example:
|
|
134
|
-
>>> # By name (creates if not exists)
|
|
135
|
-
>>> mcp = client.mcps.upsert_mcp(
|
|
136
|
-
... "deepwiki",
|
|
137
|
-
... transport="sse",
|
|
138
|
-
... config={"url": "https://mcp.deepwiki.com/sse"},
|
|
139
|
-
... )
|
|
140
|
-
>>> # By instance
|
|
141
|
-
>>> mcp = client.mcps.upsert_mcp(existing_mcp, description="Updated")
|
|
142
|
-
>>> # By ID
|
|
143
|
-
>>> mcp = client.mcps.upsert_mcp("uuid-here", description="Updated")
|
|
144
|
-
"""
|
|
145
|
-
# Handle MCP instance
|
|
146
|
-
if isinstance(identifier, MCP):
|
|
147
|
-
if identifier.id:
|
|
148
|
-
logger.info("Updating MCP by instance: %s", identifier.name)
|
|
149
|
-
return self._do_upsert_update(identifier.id, identifier.name, description, config, **kwargs)
|
|
150
|
-
# MCP without ID - treat name as identifier
|
|
151
|
-
identifier = identifier.name
|
|
152
|
-
|
|
153
|
-
# Handle string (ID or name)
|
|
154
|
-
if isinstance(identifier, str):
|
|
155
|
-
if is_uuid(identifier):
|
|
156
|
-
logger.info("Updating MCP by ID: %s", identifier)
|
|
157
|
-
existing = self.get_mcp_by_id(identifier)
|
|
158
|
-
return self._do_upsert_update(identifier, existing.name, description, config, **kwargs)
|
|
159
|
-
|
|
160
|
-
# It's a name - find or create
|
|
161
|
-
return self._upsert_by_name(identifier, description, config, **kwargs)
|
|
162
|
-
|
|
163
|
-
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
164
|
-
|
|
165
|
-
def _do_upsert_update(
|
|
166
|
-
self,
|
|
167
|
-
mcp_id: str,
|
|
168
|
-
name: str | None,
|
|
169
|
-
description: str | None,
|
|
170
|
-
config: dict[str, Any] | None,
|
|
171
|
-
**kwargs,
|
|
172
|
-
) -> MCP:
|
|
173
|
-
"""Perform the update part of upsert."""
|
|
174
|
-
update_kwargs = {**kwargs}
|
|
175
|
-
if name is not None:
|
|
176
|
-
update_kwargs["name"] = name
|
|
177
|
-
if description is not None:
|
|
178
|
-
update_kwargs["description"] = description
|
|
179
|
-
if config is not None:
|
|
180
|
-
update_kwargs["config"] = config
|
|
181
|
-
return self.update_mcp(mcp_id, **update_kwargs)
|
|
182
|
-
|
|
183
|
-
def _upsert_by_name(
|
|
184
|
-
self,
|
|
185
|
-
name: str,
|
|
186
|
-
description: str | None,
|
|
187
|
-
config: dict[str, Any] | None,
|
|
188
|
-
**kwargs,
|
|
189
|
-
) -> MCP:
|
|
190
|
-
"""Find by name and update, or create if not found."""
|
|
191
|
-
existing = self.find_mcps(name)
|
|
192
|
-
|
|
193
|
-
if len(existing) == 1:
|
|
194
|
-
logger.info("Updating existing MCP: %s", name)
|
|
195
|
-
return self._do_upsert_update(existing[0].id, name, description, config, **kwargs)
|
|
196
|
-
|
|
197
|
-
if len(existing) > 1:
|
|
198
|
-
raise ValueError(f"Multiple MCPs found with name '{name}'")
|
|
199
|
-
|
|
200
|
-
# Create new MCP
|
|
201
|
-
logger.info("Creating new MCP: %s", name)
|
|
202
|
-
return self.create_mcp(name=name, description=description, config=config, **kwargs)
|
|
203
|
-
|
|
204
|
-
def _build_create_payload(
|
|
205
|
-
self,
|
|
206
|
-
name: str,
|
|
207
|
-
description: str | None = None,
|
|
208
|
-
transport: str = DEFAULT_MCP_TRANSPORT,
|
|
209
|
-
config: dict[str, Any] | None = None,
|
|
210
|
-
**kwargs,
|
|
211
|
-
) -> dict[str, Any]:
|
|
212
|
-
"""Build payload for MCP creation with proper metadata handling.
|
|
213
|
-
|
|
214
|
-
CENTRALIZED PAYLOAD BUILDING LOGIC:
|
|
215
|
-
- Sets proper defaults and required fields
|
|
216
|
-
- Handles config serialization consistently
|
|
217
|
-
- Processes transport and other metadata properly
|
|
218
|
-
|
|
219
|
-
Args:
|
|
220
|
-
name: MCP name
|
|
221
|
-
description: MCP description (optional)
|
|
222
|
-
transport: MCP transport protocol (defaults to stdio)
|
|
223
|
-
config: MCP configuration dictionary
|
|
224
|
-
**kwargs: Additional parameters
|
|
225
|
-
|
|
226
|
-
Returns:
|
|
227
|
-
Complete payload dictionary for MCP creation
|
|
228
|
-
"""
|
|
229
|
-
# Prepare the creation payload with required fields
|
|
230
|
-
payload: dict[str, Any] = {
|
|
231
|
-
"name": name.strip(),
|
|
232
|
-
"type": DEFAULT_MCP_TYPE, # MCPs are always server type
|
|
233
|
-
"transport": transport,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
# Add description if provided
|
|
237
|
-
if description:
|
|
238
|
-
payload["description"] = description.strip()
|
|
239
|
-
|
|
240
|
-
# Handle config - ensure it's properly serialized
|
|
241
|
-
if config:
|
|
242
|
-
payload["config"] = config
|
|
243
|
-
|
|
244
|
-
# Add any other kwargs (excluding already handled ones)
|
|
245
|
-
excluded_keys = {"type"} # type is handled above
|
|
246
|
-
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
247
|
-
|
|
248
|
-
return payload
|
|
249
|
-
|
|
250
|
-
def _build_update_payload(
|
|
251
|
-
self,
|
|
252
|
-
current_mcp: MCP,
|
|
253
|
-
name: str | None = None,
|
|
254
|
-
description: str | None = None,
|
|
255
|
-
**kwargs,
|
|
256
|
-
) -> dict[str, Any]:
|
|
257
|
-
"""Build payload for MCP update with proper current state preservation.
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
current_mcp: Current MCP object to update
|
|
261
|
-
name: New MCP name (None to keep current)
|
|
262
|
-
description: New description (None to keep current)
|
|
263
|
-
**kwargs: Additional parameters (config, transport, etc.)
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
Complete payload dictionary for MCP update
|
|
267
|
-
|
|
268
|
-
Notes:
|
|
269
|
-
- Preserves current values as defaults when new values not provided
|
|
270
|
-
- Handles config updates properly
|
|
271
|
-
"""
|
|
272
|
-
# Prepare the update payload with current values as defaults
|
|
273
|
-
update_data = {
|
|
274
|
-
"name": name if name is not None else current_mcp.name,
|
|
275
|
-
"type": DEFAULT_MCP_TYPE, # Required by backend, MCPs are always server type
|
|
276
|
-
"transport": kwargs.get("transport", getattr(current_mcp, "transport", DEFAULT_MCP_TRANSPORT)),
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
# Handle description with proper None handling
|
|
280
|
-
if description is not None:
|
|
281
|
-
update_data["description"] = description.strip()
|
|
282
|
-
elif hasattr(current_mcp, "description") and current_mcp.description:
|
|
283
|
-
update_data["description"] = current_mcp.description
|
|
284
|
-
|
|
285
|
-
# Handle config with proper merging
|
|
286
|
-
if "config" in kwargs:
|
|
287
|
-
update_data["config"] = kwargs["config"]
|
|
288
|
-
elif hasattr(current_mcp, "config") and current_mcp.config:
|
|
289
|
-
# Preserve existing config if present
|
|
290
|
-
update_data["config"] = current_mcp.config
|
|
291
|
-
|
|
292
|
-
# Add any other kwargs (excluding already handled ones)
|
|
293
|
-
excluded_keys = {"transport", "config"}
|
|
294
|
-
for key, value in kwargs.items():
|
|
295
|
-
if key not in excluded_keys:
|
|
296
|
-
update_data[key] = value
|
|
297
|
-
|
|
298
|
-
return update_data
|
|
299
|
-
|
|
300
|
-
def get_mcp_tools(self, mcp_id: str) -> list[dict[str, Any]]:
|
|
301
|
-
"""Get tools available from an MCP."""
|
|
302
|
-
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}/tools")
|
|
303
|
-
if data is None:
|
|
304
|
-
return []
|
|
305
|
-
if isinstance(data, list):
|
|
306
|
-
return data
|
|
307
|
-
if isinstance(data, dict):
|
|
308
|
-
if "tools" in data:
|
|
309
|
-
return data.get("tools", []) or []
|
|
310
|
-
logger.warning(
|
|
311
|
-
"Unexpected MCP tools response keys %s; returning empty list",
|
|
312
|
-
list(data.keys()),
|
|
313
|
-
)
|
|
314
|
-
return []
|
|
315
|
-
logger.warning(
|
|
316
|
-
"Unexpected MCP tools response type %s; returning empty list",
|
|
317
|
-
type(data).__name__,
|
|
318
|
-
)
|
|
319
|
-
return []
|
|
320
|
-
|
|
321
|
-
def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
322
|
-
"""Test MCP connection using configuration.
|
|
323
|
-
|
|
324
|
-
Args:
|
|
325
|
-
config: MCP configuration dictionary
|
|
326
|
-
|
|
327
|
-
Returns:
|
|
328
|
-
dict: Connection test result
|
|
329
|
-
|
|
330
|
-
Raises:
|
|
331
|
-
Exception: If connection test fails
|
|
332
|
-
"""
|
|
333
|
-
try:
|
|
334
|
-
response = self._request("POST", MCPS_CONNECT_ENDPOINT, json=config)
|
|
335
|
-
return response
|
|
336
|
-
except Exception as e:
|
|
337
|
-
logger.error(f"Failed to test MCP connection: {e}")
|
|
338
|
-
raise
|
|
339
|
-
|
|
340
|
-
def test_mcp_connection_from_config(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
341
|
-
"""Test MCP connection using configuration (alias for test_mcp_connection).
|
|
342
|
-
|
|
343
|
-
Args:
|
|
344
|
-
config: MCP configuration dictionary
|
|
345
|
-
|
|
346
|
-
Returns:
|
|
347
|
-
dict: Connection test result
|
|
348
|
-
"""
|
|
349
|
-
return self.test_mcp_connection(config)
|
|
350
|
-
|
|
351
|
-
def get_mcp_tools_from_config(self, config: dict[str, Any]) -> list[dict[str, Any]]:
|
|
352
|
-
"""Fetch tools from MCP configuration without saving.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
config: MCP configuration dictionary
|
|
356
|
-
|
|
357
|
-
Returns:
|
|
358
|
-
list: List of available tools from the MCP
|
|
359
|
-
|
|
360
|
-
Raises:
|
|
361
|
-
Exception: If tool fetching fails
|
|
362
|
-
"""
|
|
363
|
-
try:
|
|
364
|
-
response = self._request("POST", MCPS_CONNECT_TOOLS_ENDPOINT, json=config)
|
|
365
|
-
if response is None:
|
|
366
|
-
return []
|
|
367
|
-
return response.get("tools", []) or []
|
|
368
|
-
except Exception as e:
|
|
369
|
-
logger.error(f"Failed to get MCP tools from config: {e}")
|
|
370
|
-
raise
|