deepctl-cmd-mcp 0.1.0__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.
- deepctl_cmd_mcp-0.1.0/PKG-INFO +32 -0
- deepctl_cmd_mcp-0.1.0/README.md +10 -0
- deepctl_cmd_mcp-0.1.0/pyproject.toml +50 -0
- deepctl_cmd_mcp-0.1.0/setup.cfg +4 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp/__init__.py +14 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp/__main__.py +8 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp/command.py +387 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp/gnosis.py +372 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp/models.py +24 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/PKG-INFO +32 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/SOURCES.txt +13 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/dependency_links.txt +1 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/entry_points.txt +2 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/requires.txt +14 -0
- deepctl_cmd_mcp-0.1.0/src/deepctl_cmd_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server command for deepctl to interact with Deepgram's AI assistant service
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
11
|
+
Requires-Dist: rich>=13.9.4
|
|
12
|
+
Requires-Dist: click>=8.1.7
|
|
13
|
+
Requires-Dist: pydantic>=2.10.1
|
|
14
|
+
Requires-Dist: httpx>=0.28.1
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8.3.3; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy>=1.13.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.8.1; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# deepctl-cmd-mcp
|
|
24
|
+
|
|
25
|
+
This package provides two ways to interact with Deepgram's AI assistant:
|
|
26
|
+
|
|
27
|
+
1. **MCP Server**: Run an MCP server that exposes Deepgram's AI assistant tools
|
|
28
|
+
2. **Python Client**: Standalone Python client for interacting with Deepgram's AI API
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
This package is installed as part of the main deepctl CLI.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# deepctl-cmd-mcp
|
|
2
|
+
|
|
3
|
+
This package provides two ways to interact with Deepgram's AI assistant:
|
|
4
|
+
|
|
5
|
+
1. **MCP Server**: Run an MCP server that exposes Deepgram's AI assistant tools
|
|
6
|
+
2. **Python Client**: Standalone Python client for interacting with Deepgram's AI API
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
This package is installed as part of the main deepctl CLI.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "deepctl-cmd-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server command for deepctl to interact with Deepgram's AI assistant service"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"deepctl-core>=0.1.0",
|
|
15
|
+
"mcp>=1.0.0",
|
|
16
|
+
"rich>=13.9.4",
|
|
17
|
+
"click>=8.1.7",
|
|
18
|
+
"pydantic>=2.10.1",
|
|
19
|
+
"httpx>=0.28.1",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.entry-points."deepctl.commands"]
|
|
23
|
+
mcp = "deepctl_cmd_mcp.command:McpCommand"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8.3.3",
|
|
28
|
+
"pytest-asyncio>=0.24.0",
|
|
29
|
+
"pytest-cov>=6.0.0",
|
|
30
|
+
"pytest-mock>=3.14.0",
|
|
31
|
+
"mypy>=1.13.0",
|
|
32
|
+
"ruff>=0.8.1",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
include = ["deepctl_cmd_mcp*"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 88
|
|
41
|
+
target-version = "py310"
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["E", "F", "I", "N", "W", "B", "Q"]
|
|
45
|
+
ignore = ["E501"]
|
|
46
|
+
|
|
47
|
+
[tool.mypy]
|
|
48
|
+
python_version = "3.10"
|
|
49
|
+
warn_return_any = true
|
|
50
|
+
warn_unused_configs = true
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""MCP server command for deepctl."""
|
|
2
|
+
|
|
3
|
+
from .command import McpCommand
|
|
4
|
+
from .gnosis import GnosisClient, GnosisRequest, GnosisResponse
|
|
5
|
+
from .models import MCPServerResult, TransportType
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"McpCommand",
|
|
9
|
+
"GnosisClient",
|
|
10
|
+
"GnosisRequest",
|
|
11
|
+
"GnosisResponse",
|
|
12
|
+
"MCPServerResult",
|
|
13
|
+
"TransportType",
|
|
14
|
+
]
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""MCP server command for deepctl."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from deepctl_core import AuthManager, BaseCommand, Config, DeepgramClient
|
|
7
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from .gnosis import GnosisClient
|
|
11
|
+
from .models import MCPServerResult, TransportType
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class McpCommand(BaseCommand):
|
|
17
|
+
"""MCP server command for interacting with Deepgram's AI assistant."""
|
|
18
|
+
|
|
19
|
+
name = "mcp"
|
|
20
|
+
help = "Run an MCP server that connects to Deepgram's AI assistant service"
|
|
21
|
+
short_help = "Run MCP server for Deepgram AI"
|
|
22
|
+
|
|
23
|
+
# MCP doesn't require existing auth to start, but can use it
|
|
24
|
+
requires_auth = False
|
|
25
|
+
requires_project = False
|
|
26
|
+
ci_friendly = True
|
|
27
|
+
|
|
28
|
+
def get_arguments(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""Get command arguments and options."""
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
"names": ["--transport", "-t"],
|
|
33
|
+
"help": (
|
|
34
|
+
"Transport mode: stdio (default), sse, "
|
|
35
|
+
"or streamable-http"
|
|
36
|
+
),
|
|
37
|
+
"type": str,
|
|
38
|
+
"default": "stdio",
|
|
39
|
+
"required": False,
|
|
40
|
+
"is_option": True,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"names": ["--port", "-p"],
|
|
44
|
+
"help": "Port number for HTTP server (default: 8000)",
|
|
45
|
+
"type": int,
|
|
46
|
+
"default": 8000,
|
|
47
|
+
"required": False,
|
|
48
|
+
"is_option": True,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"names": ["--host"],
|
|
52
|
+
"help": "Host address for HTTP server (default: 127.0.0.1)",
|
|
53
|
+
"type": str,
|
|
54
|
+
"default": "127.0.0.1",
|
|
55
|
+
"required": False,
|
|
56
|
+
"is_option": True,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"names": ["--api-key"],
|
|
60
|
+
"help": (
|
|
61
|
+
"Override API key for Deepgram AI service (falls back to "
|
|
62
|
+
"profile or DEEPGRAM_API_KEY env var)"
|
|
63
|
+
),
|
|
64
|
+
"type": str,
|
|
65
|
+
"required": False,
|
|
66
|
+
"is_option": True,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"names": ["--gnosis-url"],
|
|
70
|
+
"help": "Base URL for Deepgram AI service",
|
|
71
|
+
"type": str,
|
|
72
|
+
"default": "https://gnosis.deepgram.com",
|
|
73
|
+
"required": False,
|
|
74
|
+
"is_option": True,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"names": ["--debug"],
|
|
78
|
+
"help": "Enable debug logging",
|
|
79
|
+
"is_flag": True,
|
|
80
|
+
"is_option": True,
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
def handle(
|
|
85
|
+
self,
|
|
86
|
+
config: Config,
|
|
87
|
+
auth_manager: AuthManager,
|
|
88
|
+
client: DeepgramClient,
|
|
89
|
+
**kwargs: Any,
|
|
90
|
+
) -> Any:
|
|
91
|
+
"""Handle MCP server command."""
|
|
92
|
+
transport = kwargs.get("transport", "stdio").lower()
|
|
93
|
+
port = kwargs.get("port", 8000)
|
|
94
|
+
host = kwargs.get("host", "127.0.0.1")
|
|
95
|
+
gnosis_api_key = kwargs.get("api_key") or os.getenv("DEEPGRAM_API_KEY")
|
|
96
|
+
# Fallback to stored credentials if no key provided
|
|
97
|
+
if not gnosis_api_key:
|
|
98
|
+
gnosis_api_key = auth_manager.get_api_key()
|
|
99
|
+
gnosis_url = kwargs.get("gnosis_url", "https://gnosis.deepgram.com")
|
|
100
|
+
debug = kwargs.get("debug", False)
|
|
101
|
+
|
|
102
|
+
# Validate transport type
|
|
103
|
+
valid_transports = ["stdio", "sse", "streamable-http"]
|
|
104
|
+
if transport not in valid_transports:
|
|
105
|
+
console.print(
|
|
106
|
+
f"[red]Invalid transport type:[/red] {transport}. "
|
|
107
|
+
f"Must be one of: {', '.join(valid_transports)}"
|
|
108
|
+
)
|
|
109
|
+
return MCPServerResult(
|
|
110
|
+
status="error", message=f"Invalid transport type: {transport}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Store configuration in environment for the MCP server
|
|
114
|
+
if isinstance(gnosis_api_key, str) and gnosis_api_key:
|
|
115
|
+
# Expose for child processes / downstream code
|
|
116
|
+
os.environ["DEEPGRAM_API_KEY"] = gnosis_api_key
|
|
117
|
+
os.environ["DEEPGRAM_GNOSIS_URL"] = gnosis_url
|
|
118
|
+
if debug:
|
|
119
|
+
os.environ["DEEPGRAM_MCP_DEBUG"] = "1"
|
|
120
|
+
|
|
121
|
+
# Create and run the MCP server
|
|
122
|
+
mcp_server = create_mcp_server()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
if transport == "stdio":
|
|
126
|
+
console.print(
|
|
127
|
+
"[blue]Starting MCP server in stdio mode...[/blue]"
|
|
128
|
+
)
|
|
129
|
+
mcp_server.run()
|
|
130
|
+
elif transport == "sse":
|
|
131
|
+
console.print(
|
|
132
|
+
f"[blue]Starting MCP SSE server on {host}:{port}...[/blue]"
|
|
133
|
+
)
|
|
134
|
+
mcp_server.run(transport="sse")
|
|
135
|
+
elif transport == "streamable-http":
|
|
136
|
+
console.print(
|
|
137
|
+
f"[blue]Starting MCP Streamable HTTP server on "
|
|
138
|
+
f"{host}:{port}...[/blue]"
|
|
139
|
+
)
|
|
140
|
+
mcp_server.run(transport="streamable-http")
|
|
141
|
+
|
|
142
|
+
return MCPServerResult(
|
|
143
|
+
status="success",
|
|
144
|
+
message="MCP server stopped",
|
|
145
|
+
transport=TransportType(transport.replace("-", "")),
|
|
146
|
+
port=port if transport != "stdio" else None,
|
|
147
|
+
host=host if transport != "stdio" else None,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
console.print("\n[yellow]MCP server stopped by user[/yellow]")
|
|
152
|
+
return MCPServerResult(
|
|
153
|
+
status="cancelled",
|
|
154
|
+
message="MCP server stopped by user",
|
|
155
|
+
transport=TransportType(transport.replace("-", "")),
|
|
156
|
+
port=port if transport != "stdio" else None,
|
|
157
|
+
host=host if transport != "stdio" else None,
|
|
158
|
+
)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
console.print(f"[red]Error running MCP server:[/red] {e}")
|
|
161
|
+
return MCPServerResult(
|
|
162
|
+
status="error",
|
|
163
|
+
message=str(e),
|
|
164
|
+
transport=TransportType(transport.replace("-", "")),
|
|
165
|
+
port=port if transport != "stdio" else None,
|
|
166
|
+
host=host if transport != "stdio" else None,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def create_mcp_server() -> FastMCP:
|
|
171
|
+
"""Create the MCP server with Deepgram AI assistant tools."""
|
|
172
|
+
# Create FastMCP instance
|
|
173
|
+
mcp = FastMCP("Deepgram AI Assistant")
|
|
174
|
+
|
|
175
|
+
# Get configuration from environment
|
|
176
|
+
gnosis_api_key = os.getenv("DEEPGRAM_API_KEY")
|
|
177
|
+
gnosis_url = os.getenv(
|
|
178
|
+
"DEEPGRAM_GNOSIS_URL", "https://gnosis.deepgram.com"
|
|
179
|
+
)
|
|
180
|
+
debug = os.getenv("DEEPGRAM_MCP_DEBUG", "").lower() in ("1", "true", "yes")
|
|
181
|
+
|
|
182
|
+
# Debug logging to diagnose URL issue
|
|
183
|
+
if debug:
|
|
184
|
+
import sys
|
|
185
|
+
|
|
186
|
+
print(
|
|
187
|
+
f"[DEBUG] DEEPGRAM_GNOSIS_URL from env: {gnosis_url}",
|
|
188
|
+
file=sys.stderr,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Create a shared Gnosis client instance
|
|
192
|
+
try:
|
|
193
|
+
gnosis_client = GnosisClient(
|
|
194
|
+
api_key=gnosis_api_key,
|
|
195
|
+
base_url=gnosis_url,
|
|
196
|
+
debug=debug,
|
|
197
|
+
)
|
|
198
|
+
except ValueError:
|
|
199
|
+
# Client will be None if no API key is available
|
|
200
|
+
gnosis_client = None
|
|
201
|
+
|
|
202
|
+
@mcp.tool()
|
|
203
|
+
async def ask_question(question: str, ctx: Context[Any, Any, Any]) -> str:
|
|
204
|
+
"""Ask questions about Deepgram products and services.
|
|
205
|
+
|
|
206
|
+
This is the **catch-all** helper for natural-language queries about
|
|
207
|
+
anything related to Deepgram. Use it for general product or feature
|
|
208
|
+
questions, onboarding advice ("How do I get started with live
|
|
209
|
+
transcription?"), pricing, SDK capabilities, best practices, model
|
|
210
|
+
details, or any other information request that does **not** obviously
|
|
211
|
+
belong to one of the specialised tools below.
|
|
212
|
+
|
|
213
|
+
Example questions that should route to this tool:
|
|
214
|
+
• "What is the difference between pre-recorded and live transcription?"
|
|
215
|
+
• "Does Deepgram offer speaker diarization?"
|
|
216
|
+
• "How accurate is Nova-2 on telephone audio?"
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
question: The question to ask about Deepgram (products, services,
|
|
220
|
+
SDKs, APIs, pricing, etc.)
|
|
221
|
+
ctx: MCP context used by FastMCP
|
|
222
|
+
"""
|
|
223
|
+
if not gnosis_client:
|
|
224
|
+
return (
|
|
225
|
+
"Error: Deepgram API key not configured. Please set "
|
|
226
|
+
"DEEPGRAM_API_KEY, use the --api-key flag, or store a "
|
|
227
|
+
"credential."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if debug:
|
|
231
|
+
await ctx.info(f"Asking question: {question}")
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
return await gnosis_client.ask_question(
|
|
235
|
+
question,
|
|
236
|
+
system_prompt=(
|
|
237
|
+
"You are a helpful assistant that answers questions about "
|
|
238
|
+
"Deepgram products and services."
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
return f"Error: {str(e)}"
|
|
243
|
+
|
|
244
|
+
@mcp.tool()
|
|
245
|
+
async def check_api_spec(
|
|
246
|
+
api_type: str = "rest",
|
|
247
|
+
endpoint: str = "",
|
|
248
|
+
ctx: Optional[Context[Any, Any, Any]] = None,
|
|
249
|
+
) -> str:
|
|
250
|
+
"""Retrieve Deepgram **API reference** details.
|
|
251
|
+
|
|
252
|
+
Use this tool when the user explicitly asks for endpoint specifics,
|
|
253
|
+
HTTP verbs, WebSocket event schemas, request/response bodies,
|
|
254
|
+
authentication headers, rate limits, or error codes—anything that
|
|
255
|
+
belongs to the formal API specification.
|
|
256
|
+
|
|
257
|
+
Example queries that should trigger this tool:
|
|
258
|
+
• "Show me the REST API spec for /v1/listen"
|
|
259
|
+
• "What fields does the WebSocket start message accept?"
|
|
260
|
+
• "Which status codes can the Transcription API return?"
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
api_type: Either 'rest' or 'websocket'. Defaults to 'rest'.
|
|
264
|
+
endpoint: Optional specific endpoint path (e.g., "/v1/listen").
|
|
265
|
+
Leave blank for an overview.
|
|
266
|
+
ctx: MCP context
|
|
267
|
+
"""
|
|
268
|
+
if not gnosis_client:
|
|
269
|
+
return (
|
|
270
|
+
"Error: Deepgram API key not configured. Please set "
|
|
271
|
+
"DEEPGRAM_API_KEY, use the --api-key flag, or store a "
|
|
272
|
+
"credential."
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if ctx and debug:
|
|
276
|
+
await ctx.info(f"Checking API spec: {api_type} {endpoint}")
|
|
277
|
+
|
|
278
|
+
prompt = (
|
|
279
|
+
f"Provide the API specification for Deepgram's {api_type.upper()} "
|
|
280
|
+
)
|
|
281
|
+
if endpoint:
|
|
282
|
+
prompt += f"endpoint: {endpoint}"
|
|
283
|
+
else:
|
|
284
|
+
prompt += "endpoints"
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
return await gnosis_client.ask_question(
|
|
288
|
+
prompt,
|
|
289
|
+
system_prompt=(
|
|
290
|
+
"You are a technical assistant that provides detailed API "
|
|
291
|
+
"specifications for Deepgram."
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
return f"Error: {str(e)}"
|
|
296
|
+
|
|
297
|
+
@mcp.tool()
|
|
298
|
+
async def get_code_example(
|
|
299
|
+
language: str,
|
|
300
|
+
use_case: str,
|
|
301
|
+
ctx: Optional[Context[Any, Any, Any]] = None,
|
|
302
|
+
) -> str:
|
|
303
|
+
"""Return **ready-to-run code samples** that integrate Deepgram.
|
|
304
|
+
|
|
305
|
+
Use this when the user asks for sample code or snippets in a particular
|
|
306
|
+
language or SDK, e.g. "Give me a Python example for real-time
|
|
307
|
+
transcription" or "Show a JavaScript snippet for batch processing".
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
language: Target programming language (python, javascript,
|
|
311
|
+
typescript, go, java, csharp, etc.)
|
|
312
|
+
use_case: The scenario for the snippet (e.g., "real-time
|
|
313
|
+
transcription", "batch processing").
|
|
314
|
+
ctx: MCP context
|
|
315
|
+
"""
|
|
316
|
+
if not gnosis_client:
|
|
317
|
+
return (
|
|
318
|
+
"Error: Deepgram API key not configured. Please set "
|
|
319
|
+
"DEEPGRAM_API_KEY, use the --api-key flag, or store a "
|
|
320
|
+
"credential."
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
if ctx and debug:
|
|
324
|
+
await ctx.info(f"Getting code example: {language} for {use_case}")
|
|
325
|
+
|
|
326
|
+
prompt = f"Provide a {language} code example for: {use_case}"
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
return await gnosis_client.ask_question(
|
|
330
|
+
prompt,
|
|
331
|
+
system_prompt=(
|
|
332
|
+
f"You are a code assistant that provides {language} "
|
|
333
|
+
f"code examples for Deepgram."
|
|
334
|
+
),
|
|
335
|
+
)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
return f"Error: {str(e)}"
|
|
338
|
+
|
|
339
|
+
@mcp.tool()
|
|
340
|
+
async def search_docs(
|
|
341
|
+
query: str,
|
|
342
|
+
category: str = "all",
|
|
343
|
+
ctx: Optional[Context[Any, Any, Any]] = None,
|
|
344
|
+
) -> str:
|
|
345
|
+
"""Keyword search across official Deepgram documentation.
|
|
346
|
+
|
|
347
|
+
Ideal for requests that mention "documentation", "docs", "guide",
|
|
348
|
+
or when the user wants a how-to or tutorial section. The search can
|
|
349
|
+
be narrowed to specific categories like guides, SDK references, or
|
|
350
|
+
API reference.
|
|
351
|
+
|
|
352
|
+
Example queries:
|
|
353
|
+
• "Find the docs page about audio formats"
|
|
354
|
+
• "Search the guides for diarization"
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
query: Search terms to look for.
|
|
358
|
+
category: One of 'guides', 'api-reference', 'sdks', or 'all'.
|
|
359
|
+
Defaults to 'all'.
|
|
360
|
+
ctx: MCP context
|
|
361
|
+
"""
|
|
362
|
+
if not gnosis_client:
|
|
363
|
+
return (
|
|
364
|
+
"Error: Deepgram API key not configured. Please set "
|
|
365
|
+
"DEEPGRAM_API_KEY, use the --api-key flag, or store a "
|
|
366
|
+
"credential."
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if ctx and debug:
|
|
370
|
+
await ctx.info(f"Searching docs: {query} in {category}")
|
|
371
|
+
|
|
372
|
+
search_prompt = f"Search Deepgram documentation for: {query}"
|
|
373
|
+
if category != "all":
|
|
374
|
+
search_prompt += f" (category: {category})"
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
return await gnosis_client.ask_question(
|
|
378
|
+
search_prompt,
|
|
379
|
+
system_prompt=(
|
|
380
|
+
"You are a documentation assistant that helps find "
|
|
381
|
+
"relevant information in Deepgram's docs."
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
except Exception as e:
|
|
385
|
+
return f"Error: {str(e)}"
|
|
386
|
+
|
|
387
|
+
return mcp
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""Gnosis API client for Deepgram's AI assistant service."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GnosisRequest(BaseModel):
|
|
14
|
+
"""Request model for Gnosis API."""
|
|
15
|
+
|
|
16
|
+
messages: List[Dict[str, str]] = Field(
|
|
17
|
+
..., description="List of messages in the conversation"
|
|
18
|
+
)
|
|
19
|
+
model: str = Field(
|
|
20
|
+
default="deepgram", description="Model to use for generation"
|
|
21
|
+
)
|
|
22
|
+
temperature: float = Field(
|
|
23
|
+
default=0.7, description="Temperature for generation"
|
|
24
|
+
)
|
|
25
|
+
max_tokens: Optional[int] = Field(
|
|
26
|
+
default=None, description="Maximum tokens to generate"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class GnosisResponse(BaseModel):
|
|
31
|
+
"""Response model from Gnosis API."""
|
|
32
|
+
|
|
33
|
+
choices: List[Dict[str, Any]] = Field(
|
|
34
|
+
default_factory=list, description="Response choices"
|
|
35
|
+
)
|
|
36
|
+
usage: Optional[Dict[str, Any]] = Field(
|
|
37
|
+
default=None, description="Token usage information"
|
|
38
|
+
)
|
|
39
|
+
model: Optional[str] = Field(default=None, description="Model used")
|
|
40
|
+
id: Optional[str] = Field(default=None, description="Response ID")
|
|
41
|
+
created: Optional[int] = Field(
|
|
42
|
+
default=None, description="Creation timestamp"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GnosisClient:
|
|
47
|
+
"""Client for interacting with Deepgram's Gnosis API."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
api_key: Optional[str] = None,
|
|
52
|
+
base_url: str = "https://gnosis.deepgram.com",
|
|
53
|
+
timeout: float = 30.0,
|
|
54
|
+
debug: bool = False,
|
|
55
|
+
):
|
|
56
|
+
"""Initialize the Gnosis client.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
api_key: Deepgram API key. If not provided, will check
|
|
60
|
+
DEEPGRAM_API_KEY env var.
|
|
61
|
+
base_url: Base URL for Gnosis API.
|
|
62
|
+
timeout: Request timeout in seconds.
|
|
63
|
+
debug: Enable debug logging.
|
|
64
|
+
"""
|
|
65
|
+
self.api_key = api_key or os.getenv("DEEPGRAM_API_KEY")
|
|
66
|
+
self.base_url = base_url.rstrip("/")
|
|
67
|
+
self.timeout = timeout
|
|
68
|
+
self.debug = debug
|
|
69
|
+
|
|
70
|
+
if not self.api_key:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"API key is required. Provide it as a parameter or set "
|
|
73
|
+
"DEEPGRAM_API_KEY environment variable."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def call(
|
|
77
|
+
self,
|
|
78
|
+
messages: List[Dict[str, str]],
|
|
79
|
+
model: str = "deepgram",
|
|
80
|
+
temperature: float = 0.7,
|
|
81
|
+
max_tokens: Optional[int] = None,
|
|
82
|
+
) -> str:
|
|
83
|
+
"""Make a request to the Gnosis API.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
messages: List of message dictionaries with 'role' and 'content'
|
|
87
|
+
keys.
|
|
88
|
+
model: Model to use for generation.
|
|
89
|
+
temperature: Temperature for generation.
|
|
90
|
+
max_tokens: Maximum tokens to generate.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The response content from Gnosis.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
httpx.HTTPStatusError: If the API returns an error status.
|
|
97
|
+
Exception: For other errors during the request.
|
|
98
|
+
"""
|
|
99
|
+
request = GnosisRequest(
|
|
100
|
+
messages=messages,
|
|
101
|
+
model=model,
|
|
102
|
+
temperature=temperature,
|
|
103
|
+
max_tokens=max_tokens,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if self.debug:
|
|
107
|
+
print(
|
|
108
|
+
f"[DEBUG] Request: {request.model_dump_json()}",
|
|
109
|
+
file=sys.stderr,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async with httpx.AsyncClient() as client:
|
|
113
|
+
try:
|
|
114
|
+
response = await client.post(
|
|
115
|
+
f"{self.base_url}/v1/chat/completions",
|
|
116
|
+
json=request.model_dump(),
|
|
117
|
+
headers={
|
|
118
|
+
"Authorization": f"token {self.api_key}",
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
},
|
|
121
|
+
timeout=self.timeout,
|
|
122
|
+
)
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
|
|
125
|
+
if self.debug:
|
|
126
|
+
print(
|
|
127
|
+
f"[DEBUG] Response: {response.text}", file=sys.stderr
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
gnosis_response = GnosisResponse(**response.json())
|
|
131
|
+
if gnosis_response.choices and gnosis_response.choices[0].get(
|
|
132
|
+
"message", {}
|
|
133
|
+
).get("content"):
|
|
134
|
+
return str(
|
|
135
|
+
gnosis_response.choices[0]["message"]["content"]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return "No response from Deepgram AI"
|
|
139
|
+
except httpx.HTTPStatusError as e:
|
|
140
|
+
if self.debug:
|
|
141
|
+
print(f"[DEBUG] HTTP Error: {e}", file=sys.stderr)
|
|
142
|
+
print(
|
|
143
|
+
f"[DEBUG] Response: {e.response.text}", file=sys.stderr
|
|
144
|
+
)
|
|
145
|
+
return (
|
|
146
|
+
f"HTTP Error {e.response.status_code}: {e.response.text}"
|
|
147
|
+
)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
error_msg = f"Error calling Deepgram AI: {str(e)}"
|
|
150
|
+
if self.debug:
|
|
151
|
+
import traceback
|
|
152
|
+
|
|
153
|
+
print(f"[DEBUG] {error_msg}", file=sys.stderr)
|
|
154
|
+
traceback.print_exc()
|
|
155
|
+
return error_msg
|
|
156
|
+
|
|
157
|
+
async def ask_question(
|
|
158
|
+
self, question: str, system_prompt: Optional[str] = None
|
|
159
|
+
) -> str:
|
|
160
|
+
"""Ask a question to Gnosis with an optional system prompt.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
question: The question to ask.
|
|
164
|
+
system_prompt: Optional system prompt to set context.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The response from Gnosis.
|
|
168
|
+
"""
|
|
169
|
+
messages = []
|
|
170
|
+
if system_prompt:
|
|
171
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
172
|
+
messages.append({"role": "user", "content": question})
|
|
173
|
+
|
|
174
|
+
return await self.call(messages)
|
|
175
|
+
|
|
176
|
+
async def chat(
|
|
177
|
+
self,
|
|
178
|
+
messages: List[Dict[str, str]],
|
|
179
|
+
system_prompt: Optional[str] = None,
|
|
180
|
+
) -> str:
|
|
181
|
+
"""Have a conversation with Gnosis.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
messages: List of messages in the conversation.
|
|
185
|
+
system_prompt: Optional system prompt to prepend.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The response from Gnosis.
|
|
189
|
+
"""
|
|
190
|
+
all_messages = []
|
|
191
|
+
if system_prompt:
|
|
192
|
+
all_messages.append({"role": "system", "content": system_prompt})
|
|
193
|
+
all_messages.extend(messages)
|
|
194
|
+
|
|
195
|
+
return await self.call(all_messages)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def main() -> None:
|
|
199
|
+
"""Main function for standalone CLI usage."""
|
|
200
|
+
import argparse
|
|
201
|
+
|
|
202
|
+
# Create parser
|
|
203
|
+
parser = argparse.ArgumentParser(
|
|
204
|
+
prog="deepctl_cmd_mcp.gnosis",
|
|
205
|
+
description="Interact with Deepgram's AI assistant API",
|
|
206
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
207
|
+
epilog="""
|
|
208
|
+
Examples:
|
|
209
|
+
# Ask a single question
|
|
210
|
+
python -m deepctl_cmd_mcp.gnosis "What is Deepgram?"
|
|
211
|
+
|
|
212
|
+
# Ask with a custom system prompt
|
|
213
|
+
python -m deepctl_cmd_mcp.gnosis "Explain streaming" \\
|
|
214
|
+
--system-prompt "You are a technical expert"
|
|
215
|
+
|
|
216
|
+
# Interactive chat mode
|
|
217
|
+
python -m deepctl_cmd_mcp.gnosis --chat
|
|
218
|
+
|
|
219
|
+
# JSON output for integration
|
|
220
|
+
python -m deepctl_cmd_mcp.gnosis "What models are available?" \\
|
|
221
|
+
--json-output
|
|
222
|
+
|
|
223
|
+
# Debug mode to see API calls
|
|
224
|
+
python -m deepctl_cmd_mcp.gnosis "Test question" --debug
|
|
225
|
+
""",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
"question",
|
|
230
|
+
nargs="?",
|
|
231
|
+
help="Question to ask Deepgram AI (omit for interactive chat mode)",
|
|
232
|
+
)
|
|
233
|
+
parser.add_argument(
|
|
234
|
+
"--url",
|
|
235
|
+
type=str,
|
|
236
|
+
default="https://gnosis.deepgram.com",
|
|
237
|
+
help="Base URL for Deepgram AI API",
|
|
238
|
+
)
|
|
239
|
+
parser.add_argument(
|
|
240
|
+
"--api-key",
|
|
241
|
+
help="Deepgram API key (defaults to DEEPGRAM_API_KEY env var)",
|
|
242
|
+
)
|
|
243
|
+
parser.add_argument(
|
|
244
|
+
"--system-prompt",
|
|
245
|
+
help="System prompt to set context",
|
|
246
|
+
)
|
|
247
|
+
parser.add_argument(
|
|
248
|
+
"--chat",
|
|
249
|
+
action="store_true",
|
|
250
|
+
help="Enable interactive chat mode",
|
|
251
|
+
)
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"--model",
|
|
254
|
+
default="deepgram",
|
|
255
|
+
help="Model to use (default: deepgram)",
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument(
|
|
258
|
+
"--temperature",
|
|
259
|
+
type=float,
|
|
260
|
+
default=0.7,
|
|
261
|
+
help="Temperature for generation (default: 0.7)",
|
|
262
|
+
)
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"--max-tokens",
|
|
265
|
+
type=int,
|
|
266
|
+
help="Maximum tokens to generate",
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument(
|
|
269
|
+
"--debug",
|
|
270
|
+
action="store_true",
|
|
271
|
+
help="Enable debug output",
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"--json-output",
|
|
275
|
+
action="store_true",
|
|
276
|
+
help="Output raw JSON response",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
args = parser.parse_args()
|
|
280
|
+
|
|
281
|
+
# Check if we have a question or chat mode
|
|
282
|
+
if not args.question and not args.chat:
|
|
283
|
+
parser.print_help()
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
client = GnosisClient(
|
|
288
|
+
api_key=args.api_key,
|
|
289
|
+
base_url=args.url,
|
|
290
|
+
debug=args.debug,
|
|
291
|
+
)
|
|
292
|
+
except ValueError as e:
|
|
293
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
if args.chat:
|
|
298
|
+
# Interactive chat mode
|
|
299
|
+
print("Deepgram AI Chat ('exit' to quit, 'clear' to reset)")
|
|
300
|
+
print("-" * 60)
|
|
301
|
+
|
|
302
|
+
history: list[dict[str, str]] = []
|
|
303
|
+
if args.system_prompt:
|
|
304
|
+
print(f"System: {args.system_prompt}")
|
|
305
|
+
print("-" * 60)
|
|
306
|
+
|
|
307
|
+
while True:
|
|
308
|
+
try:
|
|
309
|
+
user_input = input("\nYou: ").strip()
|
|
310
|
+
except (EOFError, KeyboardInterrupt):
|
|
311
|
+
print("\nGoodbye!")
|
|
312
|
+
break
|
|
313
|
+
|
|
314
|
+
if user_input.lower() in ["exit", "quit"]:
|
|
315
|
+
print("\nGoodbye!")
|
|
316
|
+
break
|
|
317
|
+
elif user_input.lower() == "clear":
|
|
318
|
+
history = []
|
|
319
|
+
print("\nChat history cleared.")
|
|
320
|
+
continue
|
|
321
|
+
elif not user_input:
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
# Add to history
|
|
325
|
+
history.append({"role": "user", "content": user_input})
|
|
326
|
+
|
|
327
|
+
# Get response
|
|
328
|
+
response = await client.chat(
|
|
329
|
+
history, system_prompt=args.system_prompt
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Add response to history
|
|
333
|
+
history.append({"role": "assistant", "content": response})
|
|
334
|
+
|
|
335
|
+
# Display response
|
|
336
|
+
print(f"\nDeepgram AI: {response}")
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
# Single question mode
|
|
340
|
+
if args.json_output:
|
|
341
|
+
# For JSON output, get the raw response
|
|
342
|
+
messages = []
|
|
343
|
+
if args.system_prompt:
|
|
344
|
+
messages.append(
|
|
345
|
+
{"role": "system", "content": args.system_prompt}
|
|
346
|
+
)
|
|
347
|
+
messages.append({"role": "user", "content": args.question})
|
|
348
|
+
|
|
349
|
+
# We need to modify the client to return the full response
|
|
350
|
+
# For now, just get the text response
|
|
351
|
+
response = await client.ask_question(
|
|
352
|
+
args.question, args.system_prompt
|
|
353
|
+
)
|
|
354
|
+
result = {
|
|
355
|
+
"question": args.question,
|
|
356
|
+
"response": response,
|
|
357
|
+
"model": args.model,
|
|
358
|
+
}
|
|
359
|
+
print(json.dumps(result, indent=2))
|
|
360
|
+
else:
|
|
361
|
+
response = await client.ask_question(
|
|
362
|
+
args.question, args.system_prompt
|
|
363
|
+
)
|
|
364
|
+
print(response)
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
368
|
+
sys.exit(1)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if __name__ == "__main__":
|
|
372
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Models for the MCP command."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TransportType(str, Enum):
|
|
10
|
+
"""Transport types for MCP server."""
|
|
11
|
+
|
|
12
|
+
STDIO = "stdio"
|
|
13
|
+
SSE = "sse"
|
|
14
|
+
STREAMABLEHTTP = "streamablehttp"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MCPServerResult(BaseModel):
|
|
18
|
+
"""Result of MCP server operation."""
|
|
19
|
+
|
|
20
|
+
status: str
|
|
21
|
+
message: str
|
|
22
|
+
transport: Optional[TransportType] = None
|
|
23
|
+
port: Optional[int] = None
|
|
24
|
+
host: Optional[str] = None
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server command for deepctl to interact with Deepgram's AI assistant service
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
11
|
+
Requires-Dist: rich>=13.9.4
|
|
12
|
+
Requires-Dist: click>=8.1.7
|
|
13
|
+
Requires-Dist: pydantic>=2.10.1
|
|
14
|
+
Requires-Dist: httpx>=0.28.1
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8.3.3; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy>=1.13.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.8.1; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# deepctl-cmd-mcp
|
|
24
|
+
|
|
25
|
+
This package provides two ways to interact with Deepgram's AI assistant:
|
|
26
|
+
|
|
27
|
+
1. **MCP Server**: Run an MCP server that exposes Deepgram's AI assistant tools
|
|
28
|
+
2. **Python Client**: Standalone Python client for interacting with Deepgram's AI API
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
This package is installed as part of the main deepctl CLI.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/deepctl_cmd_mcp/__init__.py
|
|
4
|
+
src/deepctl_cmd_mcp/__main__.py
|
|
5
|
+
src/deepctl_cmd_mcp/command.py
|
|
6
|
+
src/deepctl_cmd_mcp/gnosis.py
|
|
7
|
+
src/deepctl_cmd_mcp/models.py
|
|
8
|
+
src/deepctl_cmd_mcp.egg-info/PKG-INFO
|
|
9
|
+
src/deepctl_cmd_mcp.egg-info/SOURCES.txt
|
|
10
|
+
src/deepctl_cmd_mcp.egg-info/dependency_links.txt
|
|
11
|
+
src/deepctl_cmd_mcp.egg-info/entry_points.txt
|
|
12
|
+
src/deepctl_cmd_mcp.egg-info/requires.txt
|
|
13
|
+
src/deepctl_cmd_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_cmd_mcp
|