agentify-toolkit 0.18.2__tar.gz → 0.19.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.
Files changed (73) hide show
  1. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/PKG-INFO +2 -2
  2. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/README.md +1 -1
  3. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/pyproject.toml +1 -1
  4. agentify_toolkit-0.18.2/src/agentify/tool.py → agentify_toolkit-0.19.0/src/agentify/_tool.py +1 -1
  5. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/agent.py +151 -50
  6. agentify_toolkit-0.19.0/src/agentify/cli.py +57 -0
  7. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/__init__.py +5 -1
  8. agentify_toolkit-0.19.0/src/agentify/commands/_mcp.py +183 -0
  9. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/agent.py +1 -1
  10. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/deploy.py +1 -1
  11. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/gateway.py +1 -1
  12. agentify_toolkit-0.19.0/src/agentify/commands/mcp.py +168 -0
  13. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/provider.py +1 -1
  14. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/run.py +1 -1
  15. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/runtime.py +4 -4
  16. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/serve.py +1 -1
  17. agentify_toolkit-0.19.0/src/agentify/commands/tool.py +166 -0
  18. agentify_toolkit-0.19.0/src/agentify/headers.py +45 -0
  19. agentify_toolkit-0.19.0/src/agentify/mcp/__init__.py +2 -0
  20. agentify_toolkit-0.19.0/src/agentify/mcp/builtin_tools.py +47 -0
  21. agentify_toolkit-0.19.0/src/agentify/mcp/client.py +137 -0
  22. agentify_toolkit-0.19.0/src/agentify/mcp/registry.py +20 -0
  23. agentify_toolkit-0.19.0/src/agentify/mcp/server.py +178 -0
  24. agentify_toolkit-0.19.0/src/agentify/mcp_client.py +23 -0
  25. agentify_toolkit-0.19.0/src/agentify/mcpserver/__init__.py +1 -0
  26. agentify_toolkit-0.19.0/src/agentify/mcpserver/server.py +237 -0
  27. agentify_toolkit-0.19.0/src/agentify/tool.py +215 -0
  28. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/PKG-INFO +2 -2
  29. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/SOURCES.txt +12 -0
  30. agentify_toolkit-0.18.2/src/agentify/cli.py +0 -32
  31. agentify_toolkit-0.18.2/src/agentify/commands/tool.py +0 -72
  32. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/LICENSE +0 -0
  33. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/NOTICE +0 -0
  34. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/setup.cfg +0 -0
  35. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/__init__.py +0 -0
  36. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/_cli.py +0 -0
  37. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/cli_config.py +0 -0
  38. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/cli_ui.py +0 -0
  39. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/commands/config.py +0 -0
  40. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/gateway/server.py +0 -0
  41. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/__init__.py +0 -0
  42. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/agentify.py +0 -0
  43. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/anthropic.py +0 -0
  44. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/backplane.py +0 -0
  45. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/bedrock.py +0 -0
  46. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/deepseek.py +0 -0
  47. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/github.py +0 -0
  48. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/google.py +0 -0
  49. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/mistral.py +0 -0
  50. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/ollama.py +0 -0
  51. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/ollama_local.py +0 -0
  52. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/openai.py +0 -0
  53. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/providers/x.py +0 -0
  54. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/runtime/__init__.py +0 -0
  55. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/runtime/server.py +0 -0
  56. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/runtime_client.py +0 -0
  57. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/server/__init__.py +0 -0
  58. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/server/server.py +0 -0
  59. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/specs.py +0 -0
  60. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/agent_list.css +0 -0
  61. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/agent_list.html +0 -0
  62. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/basic-chat.css +0 -0
  63. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/chat.css +0 -0
  64. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/chat.html +0 -0
  65. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/fun_agent_list.html +0 -0
  66. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/htmx.min.js +0 -0
  67. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/retro-chat.css +0 -0
  68. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/ui/runtime_chat.html +0 -0
  69. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify/utils/env_manager.py +0 -0
  70. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/dependency_links.txt +0 -0
  71. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/entry_points.txt +0 -0
  72. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/requires.txt +0 -0
  73. {agentify_toolkit-0.18.2 → agentify_toolkit-0.19.0}/src/agentify_toolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentify_toolkit
3
- Version: 0.18.2
3
+ Version: 0.19.0
4
4
  Summary: Python Toolkit for Declarative AI Agent Development
5
5
  Author-email: Lewis Sheridan <lewis@backplane.dev>
6
6
  License: Apache License
@@ -142,7 +142,7 @@ Dynamic: license-file
142
142
 
143
143
  ![Agentify Toolkit Logo](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/agentify-logo-lg.png)
144
144
 
145
- ![agent](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/agent.png)
145
+ ![agent](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/cli.png)
146
146
 
147
147
  Agentify is a lightweight, declarative-first toolkit for prototyping AI agents. It lets you define agents as YAML specs and test them rapidly from the CLI or Python, without committing to a framework or model provider.
148
148
 
@@ -9,7 +9,7 @@
9
9
 
10
10
  ![Agentify Toolkit Logo](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/agentify-logo-lg.png)
11
11
 
12
- ![agent](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/agent.png)
12
+ ![agent](https://raw.githubusercontent.com/backplane-cloud/agentify-toolkit/main/cli.png)
13
13
 
14
14
  Agentify is a lightweight, declarative-first toolkit for prototyping AI agents. It lets you define agents as YAML specs and test them rapidly from the CLI or Python, without committing to a framework or model provider.
15
15
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentify_toolkit"
7
- version = "0.18.2"
7
+ version = "0.19.0"
8
8
  description = "Python Toolkit for Declarative AI Agent Development"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -132,7 +132,7 @@ def create_tool(spec: dict) -> Tool:
132
132
 
133
133
  # Create the Tool object
134
134
  tool = Tool(
135
- name=spec["name"],
135
+ name=f"local.{spec['name']}",
136
136
  type=spec.get("type" or "remote"),
137
137
  description=spec.get("description", ""),
138
138
  vendor=spec.get("vendor", ""),
@@ -10,6 +10,10 @@ from .specs import load_tool_spec
10
10
  from .tool import create_tool
11
11
  from pathlib import Path
12
12
 
13
+ # Import MCP Client
14
+ # from agentify.mcp_client import MCPClient
15
+ from agentify.mcp.client import MCPClientHTTP
16
+
13
17
 
14
18
  @dataclass
15
19
  class Agent:
@@ -23,6 +27,9 @@ class Agent:
23
27
  tool_names: list = field(default_factory=list)
24
28
  tools: dict = field(default_factory=dict)
25
29
 
30
+ # mcp_client: Optional["MCPClientHTTP"] = None
31
+ mcp_clients: list[MCPClientHTTP] = field(default_factory=list)
32
+
26
33
  agent_file: Path | None = None
27
34
  _tools_loaded: bool = field(default=False, init=False)
28
35
 
@@ -59,7 +66,7 @@ class Agent:
59
66
  raise FileNotFoundError(f"Tool '{tool_name}' not found at {tool_file}")
60
67
 
61
68
  spec = load_tool_spec(tool_file) # your YAML loader
62
- tool = create_tool(spec) # your tool factory
69
+ tool = create_tool(spec, tool_file) # your tool factory
63
70
  self.tools[tool.name] = tool
64
71
 
65
72
  self._tools_loaded = True
@@ -101,35 +108,71 @@ class Agent:
101
108
 
102
109
 
103
110
 
104
- def chat(agent: "Agent", debug: bool = False):
111
+ def chat(self, debug: bool = False):
105
112
  from rich.console import Console
106
113
  from rich.panel import Panel
107
114
  from rich.prompt import Prompt
108
115
 
109
- # Load Tools
110
- if agent.tool_names and not agent._tools_loaded:
111
- agent.load_tools()
116
+ # Load Tools from local Files in tools/
117
+ if self.tool_names and not self._tools_loaded:
118
+ self.load_tools()
119
+
120
+ # MCP Tool loader
121
+ # if self.mcp_client:
122
+ # self.mcp_client.initialize()
123
+ # mcp_tools = self.mcp_client.list_tools()
124
+ # mcp_tool_names = [t["name"] for t in mcp_tools]
125
+ # else:
126
+ # mcp_tool_names = []
127
+
128
+ # MCP Tool loader v2
129
+
130
+ mcp_tools = []
131
+ mcp_tool_names = []
132
+
133
+ for client in self.mcp_clients:
134
+ client.initialize()
135
+ tools = client.list_tools()
136
+ # mcp_tools += tools
137
+ # mcp_tool_names += [t["name"] for t in tools]
138
+
139
+ for tool in tools:
140
+ namespaced_tool = tool.copy()
141
+
142
+ original_name = tool["name"]
143
+ namespaced_name = f"{client.name}.{original_name}"
144
+
145
+ namespaced_tool["name"] = namespaced_name
146
+
147
+ mcp_tools.append(namespaced_tool)
148
+ mcp_tool_names.append(namespaced_name)
112
149
 
113
150
  console = Console()
114
151
 
115
152
  # Print agent header
116
153
  console.print(Panel(
117
- f"[bold cyan]{agent.name.upper()}[/bold cyan] [dim]{agent.version}[/dim]\n"
118
- f"Role: {agent.description}\n"
119
- f"Using [yellow]{agent.model_id}[/yellow] by {agent.provider}\n"
120
- f"Tools: {agent.tool_names}",
154
+ f"[bold cyan]{self.name.upper()}[/bold cyan] [dim]{self.version}[/dim]\n"
155
+ f"Role: {self.description}\n"
156
+ f"Using [yellow]{self.model_id}[/yellow] by {self.provider}\n"
157
+ f"Agent Tools: {self.tool_names}\n"
158
+ f"MCP Server Tools: {mcp_tool_names}",
121
159
  border_style="cyan"
122
160
  ))
123
161
 
124
162
 
125
- # Precompute tool schemas if any
126
- tool_schemas = [tool.to_schema() for tool in agent.tools.values()] if agent.tools else None
163
+ # Load Local Tool Schemas
164
+ tool_schemas = [tool.to_schema() for tool in self.tools.values()] if self.tools else None
127
165
  tools_block = ""
128
166
  if tool_schemas:
129
- tools_block = "\n\nTOOLS:\n" + json.dumps(tool_schemas, indent=2)
167
+ tools_block = "\n\nLOCAL TOOLS:\n" + json.dumps(tool_schemas, indent=2)
168
+
169
+ # Load MCP Server Tools
170
+ if self.mcp_clients:
171
+ tools_block += "\n\nMCP TOOLS:\n" + json.dumps(mcp_tools, indent=2)
172
+
130
173
 
131
174
  if debug:
132
- console.print(tool_schemas)
175
+ console.print(tools_block)
133
176
 
134
177
  while True:
135
178
  prompt = Prompt.ask("\nEnter your prompt ('/exit' to quit)")
@@ -138,37 +181,47 @@ class Agent:
138
181
  break
139
182
 
140
183
  # Add user input to conversation history
141
- agent.conversation_history.append({"role": "user", "content": prompt})
184
+ self.conversation_history.append({"role": "user", "content": prompt})
142
185
 
143
186
  # Build full prompt from last N turns (e.g., last 6)
144
- full_prompt = f"You must assume the role of {agent.role} when responding to these prompts:\n\n"
145
- for turn in agent.conversation_history[-6:]:
187
+ full_prompt = f"You must assume the role of {self.role} when responding to these prompts:\n\n"
188
+ for turn in self.conversation_history[-6:]:
146
189
  role = turn["role"]
147
190
  content = turn["content"]
148
- full_prompt += f"{role.upper()}: {content}\n"
191
+ full_prompt += f"Conversation History\n {role.upper()}: {content}\n"
149
192
 
150
193
  # Inject tool rules if tools exist
151
194
  if tool_schemas:
152
195
  full_prompt += """
153
- Rules for responding:
154
-
155
- If you receive the command list tools, just list them in this pattern 1. <tool name>: <description>: <arguments>: local or remote as per type
156
- 1. When invoking a tool, respond with ONLY the raw JSON object. Do not wrap it in markdown, code fences, backticks, or any other formatting. Your response must be parseable directly as JSON.
157
- 2. If the request can be answered naturally, respond in plain language.
158
- 3. When producing JSON, follow this exact format:
159
- {
160
- "tool": "<tool>",
161
- "action": "<action>",
162
- "args": {...}
163
- }
164
- 4. Never hallucinate tool use. If unsure whether a tool is needed, respond in plain language.
165
- 5. If asked what tools do you have, respond with the name and description of the tool.
196
+ Respond to user requests naturally, but if a tool must be invoked, respond with ONLY raw JSON that is parseable, with no code fences, no extra labels, and no commentary.
197
+
198
+ Rules:
199
+
200
+ 1. If the request is "list tools", respond with a numbered list of all tools in this pattern:
201
+ 1. <tool name>: <description>: <arguments>: <type> (local or remote)
202
+
203
+ 2. When invoking a tool, respond ONLY with the JSON object in this exact format:
204
+ {
205
+ "tool": "<tool name>",
206
+ "action": "<action name>",
207
+ "args": {...}
208
+ }
209
+ Do NOT wrap this in markdown, code blocks, backticks, or any extra text. The JSON must be parseable directly.
210
+
211
+ 3. Only use a tool if necessary. If no tool is needed, respond in plain language.
212
+
213
+ 4. Use the tool arguments exactly as defined in the schema.
214
+
215
+ When a user requests a tool action, produce only the JSON object following the above format.
166
216
  """
167
217
  full_prompt += tools_block
218
+
219
+ if debug:
220
+ console.print(full_prompt)
168
221
 
169
222
  # Send prompt to model
170
- with console.status(f"[blue]{agent.name} is thinking...[/blue]", spinner="dots"):
171
- response = agent.run(full_prompt)
223
+ with console.status(f"[green]{self.name.title()} is thinking...[/green]", spinner="dots"):
224
+ response = self.run(full_prompt)
172
225
 
173
226
  # Try parsing JSON (tool invocation)
174
227
  try:
@@ -180,43 +233,64 @@ class Agent:
180
233
  cleaned = cleaned[4:]
181
234
  cleaned = cleaned.strip()
182
235
 
236
+ # LLM JSON RESPONSE
183
237
  data = json.loads(cleaned)
184
238
  tool_name = data.get("tool")
185
239
  action_name = data.get("action")
186
240
  args = data.get("args", {})
187
- tool = agent.tools.get(tool_name)
188
- if not tool:
189
- raise ValueError(f"Tool '{tool_name}' not found on agent")
190
-
191
- # TOOL HANDLING
192
- if tool.type == "internal":
193
- # INTERNAL TOOL
194
- console.print(f"[yellow]INVOKING INTERNAL TOOL: '{tool_name}' with args: {args}[/yellow]", style="bold black on yellow")
195
- tool_result = tool.invoke(args=args)
241
+ tool = self.tools.get(tool_name)
242
+
243
+ if tool:
244
+ # TOOL HANDLING
245
+ if tool.type == "internal":
246
+ # Local Function Tool
247
+ console.print(f"USING LOCAL TOOL: '{tool_name}' with args: {args}", style="white on green")
248
+ tool_result = tool.invoke(args=args)
249
+ else:
250
+ # Local API Tool
251
+ console.print(f"USING LOCAL TOOL: '{tool_name}' action '{action_name}' with args: {args}", style="bold black on yellow")
252
+ tool_result = tool.invoke(action_name, args)
196
253
  else:
197
- # REMOTE TOOL
198
- console.print(f"[yellow]INVOKING REMOTE TOOL: '{tool_name}' action '{action_name}' with args: {args}[/yellow]", style="bold black on yellow")
199
- tool_result = tool.invoke(action_name, args)
254
+ # Check MCP
255
+ if tool_name in mcp_tool_names:
256
+ # MCP SERVER TOOL
257
+ console.print(f"USING MCP SERVER TOOL: '{tool_name}' with args: {args}", style="bold black on yellow")
258
+ # tool_result = self.mcp_client.call_tool(tool_name, args)
259
+ server_name, actual_tool = tool_name.split(".", 1)
260
+
261
+ client = next(
262
+ (c for c in self.mcp_clients if c.name == server_name),
263
+ None
264
+ )
265
+
266
+ if not client:
267
+ raise Exception(f"No MCP client found for server '{server_name}'")
268
+
269
+ tool_result = client.call_tool(actual_tool, args)
270
+
271
+
272
+ # if not tool:
273
+ # raise ValueError(f"Tool '{tool_name}' not found on agent")
200
274
 
201
275
  # Minify JSON to avoid confusing model in next prompt
202
276
  tool_result_str = json.dumps(tool_result, separators=(',', ':'))
203
277
 
204
278
  # Add tool output to conversation history
205
- agent.conversation_history.append({"role": "tool", "content": tool_result_str})
279
+ self.conversation_history.append({"role": "tool", "content": tool_result_str})
206
280
 
207
281
  # Ask model to display tool output naturally
208
282
  analysis_prompt = "Display the following tool data in natural language:\n" + tool_result_str
209
- with console.status(f"[blue]{agent.name} is analysing tool data...[/blue]", spinner="dots"):
210
- response = agent.run(analysis_prompt)
283
+ with console.status(f"{self.name.title()} is analysing tool response...", spinner="dots"):
284
+ response = self.run(analysis_prompt)
211
285
 
212
286
  # Store agent response in history
213
- agent.conversation_history.append({"role": "agent", "content": response})
287
+ self.conversation_history.append({"role": "agent", "content": response})
214
288
  console.print(Panel.fit(response, title="Agent Response", border_style="green"))
215
289
 
216
290
 
217
291
  except (json.JSONDecodeError, ValueError):
218
292
  # Treat as normal chat response
219
- agent.conversation_history.append({"role": "agent", "content": response})
293
+ self.conversation_history.append({"role": "agent", "content": response})
220
294
  console.print(Panel.fit(response, title="Agent Response", border_style="green"))
221
295
 
222
296
  def create_agents(specs: list) -> dict[str, Agent]:
@@ -244,8 +318,35 @@ def create_agent(spec: dict, provider: str = None, model: str = None, agent_file
244
318
  if api_key_env:
245
319
  api_key = os.getenv(api_key_env)
246
320
 
321
+ # Local Tools via tool.yaml or tool.yaml/tool.py within tools/
247
322
  tool_names = spec.get("tools")
248
323
 
249
- agent = Agent(name=name, provider=provider, model_id=model_id, role=role, description=description, version=version, tool_names=tool_names, agent_file=agent_file)
324
+ # Load MCP Server clients
325
+ mcp_clients = []
326
+
327
+ mcp_spec = spec.get("mcp", {})
328
+ servers = mcp_spec.get("servers",[])
329
+
330
+ for server in servers:
331
+ server_name = server.get("name")
332
+ endpoint = server.get("endpoint")
333
+
334
+ if endpoint:
335
+ client = MCPClientHTTP(
336
+ name=server_name,
337
+ endpoint=endpoint
338
+ )
339
+ mcp_clients.append(client)
340
+
341
+
342
+ # MCP Tools via MCP Server http://localhost:3333
343
+ # mcp_client = None
344
+ # mcp_spec = spec.get("mcp")
345
+ # if mcp_spec:
346
+ # endpoint = mcp_spec.get("endpoint")
347
+ # if endpoint:
348
+ # mcp_client = MCPClientHTTP(endpoint)
349
+
350
+ agent = Agent(name=name, provider=provider, model_id=model_id, role=role, description=description, version=version, tool_names=tool_names, agent_file=agent_file, mcp_clients=mcp_clients)
250
351
 
251
352
  return agent
@@ -0,0 +1,57 @@
1
+ import click
2
+ from agentify import __version__
3
+ from .commands import (
4
+ run_command,
5
+ serve_command,
6
+ deploy_command,
7
+ gateway_command,
8
+ runtime_group,
9
+ tool_group,
10
+ agent_group,
11
+ provider_group,
12
+ mcp_group,
13
+ xmcp_group
14
+ )
15
+
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+
19
+ console = Console()
20
+
21
+ agentify_icon = """
22
+ [white] ██ [/white]
23
+ [white] █████████████████[/white] Command: [yellow]agentify provider add <provider_name>[/yellow] [green]# e.g. openai, xai, anthropic[/green]
24
+ [white]░██ ░░███ ░░██[/white] Command: [yellow]agentify agent new[/yellow] [green]# <-- Creates a new Agent[/green]
25
+ [white]░██ ███ ██[/white] [green]# Run Agent[/green]
26
+ [white]░█████████████████[/white] Command: [yellow]agentify run agent.yaml[/yellow]
27
+ [white]░███████ ███████[/white] [green]# Start MCP Server[/green]
28
+ [white]░█████████████████[/white] Command: [yellow]agentify mcp start[/yellow]
29
+ [white]░░░██░░██░░██░░██ [/white] [green]# Start Agent Runtime → Deploy Agent[/green]
30
+ [white] ░██ ░██ ░██ ░██ [/white] Command: [yellow]agentify runtime start[/yellow] [green]→[/green] [yellow]agentify deploy agent.yaml[/yellow]
31
+ [white] ░░ ░░ ░░ ░░ [/white]
32
+ """
33
+
34
+
35
+ console.print(Panel(agentify_icon, title=f"AGENTIFY TOOLKIT CLI v{__version__}", subtitle="Build, Run and deploy AI Agents declaratively", border_style="white"))
36
+
37
+
38
+ @click.group()
39
+ @click.version_option(version=__version__, prog_name="Agentify")
40
+ def main():
41
+ """Agentify Toolkit CLI"""
42
+ pass
43
+
44
+ # Attach lazy-loaded commands
45
+ main.add_command(run_command)
46
+ main.add_command(serve_command)
47
+ main.add_command(deploy_command)
48
+ main.add_command(gateway_command)
49
+ main.add_command(runtime_group)
50
+ main.add_command(tool_group)
51
+ main.add_command(agent_group)
52
+ main.add_command(provider_group)
53
+ main.add_command(mcp_group)
54
+ main.add_command(xmcp_group)
55
+
56
+ # if __name__ == "__main__":
57
+ # main()
@@ -6,6 +6,8 @@ from .agent import agent_group
6
6
  from .provider import provider_group
7
7
  from .deploy import deploy_command
8
8
  from .gateway import gateway_command
9
+ from ._mcp import xmcp_group
10
+ from .mcp import mcp_group
9
11
 
10
12
  __all__ = [
11
13
  "run_command",
@@ -15,5 +17,7 @@ __all__ = [
15
17
  "deploy_command"
16
18
  "agent_group",
17
19
  "provider_group",
18
- "gateway_command"
20
+ "gateway_command",
21
+ "mcp_group",
22
+ "xmcp_group"
19
23
  ]
@@ -0,0 +1,183 @@
1
+ # agentify/commands/mcp.py
2
+
3
+ import click
4
+ import requests
5
+ from agentify.mcpserver.server import start_xmcp_server
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ import json
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+
12
+ console = Console()
13
+
14
+
15
+ @click.group(hidden=True)
16
+ def xmcp_group():
17
+ """Manage Agentify Tool server"""
18
+ pass
19
+
20
+
21
+ @xmcp_group.command("start")
22
+ @click.option("--host", default="127.0.0.1", help="Host to bind MCP server")
23
+ @click.option("--port", default=3333, help="Port to bind MCP server")
24
+ def start(host: str, port: int):
25
+ """Start MCP Server"""
26
+ click.echo(f"Starting MCP server on {host}:{port}")
27
+ start_mcp_server(host=host, port=port)
28
+
29
+ @xmcp_group.command("list")
30
+ @click.option("--endpoint", default="http://127.0.0.1:3333", help="MCP server endpoint")
31
+ def list_tools(endpoint: str):
32
+ """List tools exposed by the MCP server"""
33
+
34
+ url = f"{endpoint}/tools"
35
+
36
+ try:
37
+ response = requests.get(url, timeout=5)
38
+ response.raise_for_status()
39
+ except requests.RequestException as e:
40
+ console.print(f"[red]Error connecting to MCP server:[/red] {e}")
41
+ raise SystemExit(1)
42
+
43
+ data = response.json()
44
+ tools = data.get("tools", [])
45
+
46
+ if not tools:
47
+ console.print("[yellow]No tools found.[/yellow]")
48
+ return
49
+
50
+ table = Table(
51
+ title="MCP Tools",
52
+ show_header=True,
53
+ header_style="bold",
54
+ box=None, # 👈 no borders
55
+ pad_edge=False
56
+ )
57
+
58
+ table.add_column("Tool Name", style="cyan", no_wrap=True)
59
+ table.add_column("Description", style="white")
60
+
61
+ for tool in tools:
62
+ table.add_row(
63
+ tool.get("name", ""),
64
+ tool.get("description", "") or ""
65
+ )
66
+
67
+ console.print(table)
68
+
69
+ @xmcp_group.command("invoke")
70
+ @click.argument("tool_name")
71
+ @click.option(
72
+ "--args",
73
+ default="{}",
74
+ help="JSON string of arguments to pass to the tool",
75
+ )
76
+ @click.option(
77
+ "--endpoint",
78
+ default="http://127.0.0.1:3333",
79
+ help="MCP server endpoint",
80
+ )
81
+ def invoke_tool(tool_name: str, args: str, endpoint: str):
82
+ """Invoke a published tool"""
83
+ try:
84
+ arguments = json.loads(args)
85
+ except json.JSONDecodeError as e:
86
+ console.print(f"[red]Invalid JSON for --args:[/red] {e}")
87
+ raise SystemExit(1)
88
+
89
+ url = f"{endpoint}/tools/{tool_name}/invoke"
90
+
91
+ try:
92
+ response = requests.post(
93
+ url,
94
+ json={"arguments": arguments},
95
+ timeout=10,
96
+ )
97
+ response.raise_for_status()
98
+ except requests.HTTPError:
99
+ console.print(
100
+ f"[red]Tool invocation failed:[/red] {response.text}"
101
+ )
102
+ raise SystemExit(1)
103
+ except requests.RequestException as e:
104
+ console.print(f"[red]Error connecting to MCP server:[/red] {e}")
105
+ raise SystemExit(1)
106
+
107
+ data = response.json()
108
+ result = data.get("result")
109
+
110
+ # Pretty output
111
+ if isinstance(result, (dict, list)):
112
+ pretty = json.dumps(result, indent=2)
113
+ syntax = Syntax(pretty, "json", theme="ansi_dark", line_numbers=False)
114
+ console.print(Panel(syntax, title="Result"))
115
+ else:
116
+ console.print(Panel(str(result), title="Result"))
117
+
118
+ @xmcp_group.command()
119
+ @click.argument("tool_name")
120
+ @click.option(
121
+ "--server", default="http://localhost:3333",
122
+ required=False,
123
+ help="MCP server URL (e.g. http://localhost:3333)",
124
+ )
125
+ def remove(tool_name: str, server: str):
126
+ """
127
+ Remove a tool from the MCP server.
128
+ """
129
+ url = f"{server.rstrip('/')}/tools/{tool_name}"
130
+
131
+ try:
132
+ response = requests.delete(url, timeout=10)
133
+ except requests.RequestException as e:
134
+ raise click.ClickException(f"Failed to connect to MCP server: {e}")
135
+
136
+ if response.status_code != 200:
137
+ try:
138
+ detail = response.json().get("detail")
139
+ except Exception:
140
+ detail = response.text
141
+ raise click.ClickException(
142
+ f"Failed to remove tool ({response.status_code}): {detail}"
143
+ )
144
+
145
+ click.echo(f"✓ Tool removed: {tool_name}")
146
+
147
+ @xmcp_group.command("show")
148
+ @click.argument("tool_name")
149
+ @click.option("--server", default="http://localhost:3333", help="MCP server URL")
150
+ def show_tool(tool_name, server):
151
+ """Show details for a single MCP tool."""
152
+ import requests
153
+ import sys
154
+ from rich.console import Console
155
+ from rich.table import Table
156
+ from rich.json import JSON
157
+
158
+ console = Console()
159
+
160
+ try:
161
+ resp = requests.get(f"{server}/tools/{tool_name}")
162
+ resp.raise_for_status()
163
+ except requests.HTTPError as e:
164
+ console.print(f"[red]Error:[/red] {e.response.text}")
165
+ sys.exit(1)
166
+ except Exception as e:
167
+ console.print(f"[red]Error:[/red] {e}")
168
+ sys.exit(1)
169
+
170
+ tool = resp.json()
171
+
172
+ table = Table(title=f"MCP Tool: {tool['name']}", show_header=False)
173
+ table.add_column("Field", style="bold")
174
+ table.add_column("Value")
175
+
176
+ table.add_row("Name", tool["name"])
177
+ table.add_row("Description", tool.get("description", ""))
178
+
179
+ console.print(table)
180
+
181
+ if "input_schema" in tool:
182
+ console.print("\n[bold]Input Schema[/bold]")
183
+ console.print(JSON.from_data(tool["input_schema"]))
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
 
4
4
  @click.group("agent")
5
5
  def agent_group():
6
- """Create, List and Show agent YAML files"""
6
+ """Manage agent YAML definitions (create, list, show)"""
7
7
  pass
8
8
 
9
9
 
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
  )
12
12
  def deploy_command(paths, server):
13
13
  """
14
- Deploy agent YAML files to Agent Runtime server.
14
+ Deploy agents to the Agent Runtime server
15
15
 
16
16
  Examples:
17
17
  agentify deploy agent.yaml
@@ -3,7 +3,7 @@ import uvicorn
3
3
 
4
4
  @click.group(name="gateway")
5
5
  def gateway_command():
6
- """Run the Agentify model gateway."""
6
+ """Start the Agentify model gateway service"""
7
7
  pass
8
8
 
9
9
  @gateway_command.command()