chattermate-cli 0.2.0__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.
@@ -0,0 +1,253 @@
1
+ """
2
+ ChatterMate - MCP Server
3
+ Copyright (C) 2024 ChatterMate
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as
7
+ published by the Free Software Foundation, either version 3 of the
8
+ License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>
17
+
18
+ ------------------------------------------------------------------------------
19
+ ChatterMate MCP server (stdio). Exposes ChatterMate configuration to AI agents.
20
+
21
+ Configuration via environment:
22
+ CHATTERMATE_TOKEN A personal access token (cmat_...) — required.
23
+ CHATTERMATE_API_URL API base URL (default https://api.chattermate.chat).
24
+ Set to http://localhost:8000 for a local backend.
25
+
26
+ Run: chattermate-mcp (or: uvx --from chattermate-cli chattermate-mcp)
27
+ """
28
+
29
+ from typing import Any, Dict, List, Optional
30
+
31
+ from mcp.server.fastmcp import FastMCP
32
+
33
+ from . import config
34
+ from .client import ChatterMateError, Client
35
+
36
+ mcp = FastMCP("chattermate")
37
+
38
+
39
+ def _client() -> Client:
40
+ api_url = config.resolve_api_url()
41
+ token = config.resolve_token()
42
+ if not token:
43
+ raise ChatterMateError(
44
+ "No credentials. Set CHATTERMATE_TOKEN to a personal access token (cmat_...)."
45
+ )
46
+ cfg = config.load_config()
47
+ return Client(
48
+ api_url=api_url,
49
+ token=token,
50
+ refresh_token=cfg.get("refresh_token"),
51
+ )
52
+
53
+
54
+ def _org_id(client: Client) -> Optional[str]:
55
+ org = config.load_config().get("organization_id")
56
+ if org:
57
+ return org
58
+ try:
59
+ me = client.whoami()
60
+ except ChatterMateError:
61
+ return None
62
+ return me.get("organization_id") if isinstance(me, dict) else None
63
+
64
+
65
+ def _call(fn) -> Any:
66
+ """Run an API call, returning a structured error dict instead of raising."""
67
+ try:
68
+ return fn()
69
+ except ChatterMateError as e:
70
+ return {"error": str(e), "status_code": e.status_code}
71
+
72
+
73
+ # -- read-only tools -------------------------------------------------------
74
+
75
+ @mcp.tool()
76
+ def whoami() -> Any:
77
+ """Return the authenticated user (id, email, organization, role)."""
78
+ client = _client()
79
+ return _call(client.whoami)
80
+
81
+
82
+ @mcp.tool()
83
+ def list_agents() -> Any:
84
+ """List all AI agents in the organization."""
85
+ client = _client()
86
+ return _call(client.list_agents)
87
+
88
+
89
+ @mcp.tool()
90
+ def get_agent(agent_id: str) -> Any:
91
+ """Get a single agent by id."""
92
+ client = _client()
93
+ return _call(lambda: client.get_agent(agent_id))
94
+
95
+
96
+ @mcp.tool()
97
+ def get_workflow(agent_id: str) -> Any:
98
+ """Get the workflow attached to an agent."""
99
+ client = _client()
100
+ return _call(lambda: client.get_workflow_for_agent(agent_id))
101
+
102
+
103
+ @mcp.tool()
104
+ def get_workflow_nodes(workflow_id: str) -> Any:
105
+ """Get all nodes and connections for a workflow."""
106
+ client = _client()
107
+ return _call(lambda: client.get_workflow_nodes(workflow_id))
108
+
109
+
110
+ @mcp.tool()
111
+ def list_knowledge(agent_id: str) -> Any:
112
+ """List knowledge sources linked to an agent."""
113
+ client = _client()
114
+ return _call(lambda: client.list_knowledge_for_agent(agent_id))
115
+
116
+
117
+ @mcp.tool()
118
+ def get_ingestion_status(queue_id: int) -> Any:
119
+ """Get the status of a knowledge ingestion queue item."""
120
+ client = _client()
121
+ return _call(lambda: client.knowledge_queue_status(queue_id))
122
+
123
+
124
+ # -- mutating tools --------------------------------------------------------
125
+
126
+ @mcp.tool()
127
+ def create_agent(
128
+ name: str,
129
+ agent_type: str = "CUSTOM",
130
+ instructions: Optional[List[str]] = None,
131
+ description: Optional[str] = None,
132
+ display_name: Optional[str] = None,
133
+ ) -> Any:
134
+ """
135
+ Create an AI agent. agent_type is one of customer_support, sales, tech_support,
136
+ general, custom (case-insensitive). The organization is taken from the authenticated user.
137
+ """
138
+ client = _client()
139
+ org_id = _org_id(client)
140
+ if not org_id:
141
+ return {"error": "Could not determine organization for the current credentials."}
142
+ payload: Dict[str, Any] = {
143
+ "name": name,
144
+ "agent_type": agent_type.lower(),
145
+ "instructions": instructions or [f"You are {name}, a helpful assistant."],
146
+ "organization_id": org_id,
147
+ }
148
+ if description is not None:
149
+ payload["description"] = description
150
+ if display_name is not None:
151
+ payload["display_name"] = display_name
152
+ return _call(lambda: client.create_agent(payload))
153
+
154
+
155
+ @mcp.tool()
156
+ def update_agent(
157
+ agent_id: str,
158
+ instructions: Optional[List[str]] = None,
159
+ display_name: Optional[str] = None,
160
+ is_active: Optional[bool] = None,
161
+ ) -> Any:
162
+ """Update an agent's instructions, display name, or active state."""
163
+ payload: Dict[str, Any] = {}
164
+ if instructions is not None:
165
+ payload["instructions"] = instructions
166
+ if display_name is not None:
167
+ payload["display_name"] = display_name
168
+ if is_active is not None:
169
+ payload["is_active"] = is_active
170
+ if not payload:
171
+ return {"error": "Nothing to update."}
172
+ client = _client()
173
+ return _call(lambda: client.update_agent(agent_id, payload))
174
+
175
+
176
+ @mcp.tool()
177
+ def create_workflow(agent_id: str, name: str, description: Optional[str] = None) -> Any:
178
+ """Create a workflow for an agent."""
179
+ payload: Dict[str, Any] = {"name": name, "agent_id": agent_id}
180
+ if description is not None:
181
+ payload["description"] = description
182
+ client = _client()
183
+ return _call(lambda: client.create_workflow(payload))
184
+
185
+
186
+ @mcp.tool()
187
+ def update_workflow(
188
+ workflow_id: str,
189
+ name: Optional[str] = None,
190
+ description: Optional[str] = None,
191
+ status: Optional[str] = None,
192
+ ) -> Any:
193
+ """Update a workflow's metadata. status is draft, published or archived (case-insensitive)."""
194
+ payload: Dict[str, Any] = {}
195
+ if name is not None:
196
+ payload["name"] = name
197
+ if description is not None:
198
+ payload["description"] = description
199
+ if status is not None:
200
+ payload["status"] = status.lower()
201
+ if not payload:
202
+ return {"error": "Nothing to update."}
203
+ client = _client()
204
+ return _call(lambda: client.update_workflow(workflow_id, payload))
205
+
206
+
207
+ @mcp.tool()
208
+ def update_workflow_nodes(workflow_id: str, payload: Dict[str, Any]) -> Any:
209
+ """Replace a workflow's nodes/connections with the given JSON payload."""
210
+ client = _client()
211
+ return _call(lambda: client.update_workflow_nodes(workflow_id, payload))
212
+
213
+
214
+ @mcp.tool()
215
+ def add_knowledge_url(
216
+ websites: Optional[List[str]] = None,
217
+ pdf_urls: Optional[List[str]] = None,
218
+ agent_id: Optional[str] = None,
219
+ ) -> Any:
220
+ """Add website and/or PDF URLs to the knowledge base, optionally linking to an agent."""
221
+ if not websites and not pdf_urls:
222
+ return {"error": "Provide at least one website or pdf_url."}
223
+ client = _client()
224
+ org_id = _org_id(client)
225
+ if not org_id:
226
+ return {"error": "Could not determine organization for the current credentials."}
227
+ return _call(lambda: client.add_knowledge(
228
+ org_id=org_id, pdf_urls=pdf_urls or [], websites=websites or [], agent_id=agent_id,
229
+ ))
230
+
231
+
232
+ @mcp.tool()
233
+ def link_knowledge(knowledge_id: int, agent_id: str) -> Any:
234
+ """Link a knowledge source to an agent."""
235
+ client = _client()
236
+ result = _call(lambda: client.link_knowledge(knowledge_id, agent_id))
237
+ return result if result is not None else {"ok": True}
238
+
239
+
240
+ @mcp.tool()
241
+ def unlink_knowledge(knowledge_id: int, agent_id: str) -> Any:
242
+ """Unlink a knowledge source from an agent."""
243
+ client = _client()
244
+ result = _call(lambda: client.unlink_knowledge(knowledge_id, agent_id))
245
+ return result if result is not None else {"ok": True}
246
+
247
+
248
+ def main():
249
+ mcp.run(transport="stdio")
250
+
251
+
252
+ if __name__ == "__main__":
253
+ main()
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: chattermate-cli
3
+ Version: 0.2.0
4
+ Summary: ChatterMate CLI and MCP server — sign up, authenticate, and configure agents, workflows and knowledge from the terminal or an AI agent.
5
+ Author: ChatterMate
6
+ License: AGPL-3.0-or-later
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: typer>=0.12
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: rich>=13.0
13
+ Requires-Dist: mcp>=1.2
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=8.0; extra == "test"
16
+ Requires-Dist: respx>=0.21; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # ChatterMate CLI & MCP server
20
+
21
+ `chattermate-cli` provides a command-line client and an MCP (Model Context Protocol)
22
+ server for ChatterMate. A human can sign up, log in and configure agents, workflows and
23
+ knowledge from the terminal; an AI agent can do the same through the MCP server.
24
+
25
+ This is an **enterprise** feature and depends on the enterprise backend's Personal Access
26
+ Tokens (PATs).
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install -e . # from this directory, for development
32
+ # or, once published:
33
+ pipx install chattermate-cli
34
+ ```
35
+
36
+ Console scripts installed: `chattermate` (alias `cmate`) and `chattermate-mcp`.
37
+
38
+ ## Configure the API endpoint
39
+
40
+ By default the CLI talks to the hosted API at `https://api.chattermate.chat`. To target a
41
+ local or self-hosted backend, set `CHATTERMATE_API_URL=http://localhost:8000` (or pass
42
+ `--api-url`). Resolution order: `--api-url` flag → `CHATTERMATE_API_URL` env → stored config →
43
+ `https://api.chattermate.chat`.
44
+
45
+ ## Quick start (human)
46
+
47
+ ```bash
48
+ # Create an organization + admin (community signup), or log in to an existing one
49
+ chattermate signup
50
+ chattermate login --email you@acme.com
51
+
52
+ chattermate whoami
53
+
54
+ # Mint a long-lived Personal Access Token for CI / AI agents (shown once!)
55
+ chattermate token create laptop-cli
56
+ chattermate token list
57
+ chattermate token revoke <token-id>
58
+
59
+ # Configure resources
60
+ chattermate agent list
61
+ chattermate agent create --name "Support" --type CUSTOMER_SUPPORT -i "Be concise"
62
+ chattermate workflow get <agent-id>
63
+ chattermate workflow create --agent-id <agent-id> --name "Onboarding"
64
+ chattermate knowledge add-url --website https://docs.acme.com --agent-id <agent-id>
65
+ chattermate knowledge status <queue-id>
66
+ ```
67
+
68
+ Every command accepts `--json` for machine-readable output.
69
+
70
+ ## Authentication
71
+
72
+ Two credentials are supported:
73
+
74
+ - **Personal Access Token** (`cmat_...`) — preferred for non-interactive use. Pass it via
75
+ the `CHATTERMATE_TOKEN` environment variable. Long-lived and revocable.
76
+ - **JWT login** — `chattermate login` stores access + refresh tokens in
77
+ `~/.chattermate/config.json` (mode `600`) and refreshes automatically.
78
+
79
+ ## MCP server (AI agents)
80
+
81
+ `chattermate-mcp` is a stdio MCP server. Point your MCP client at it with a PAT:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "chattermate": {
87
+ "command": "uvx",
88
+ "args": ["--from", "chattermate-cli", "chattermate-mcp"],
89
+ "env": {
90
+ "CHATTERMATE_TOKEN": "cmat_...",
91
+ "CHATTERMATE_API_URL": "https://your-chattermate-host"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ `chattermate token create` prints a ready-to-paste version of this snippet.
99
+
100
+ ### Tools
101
+
102
+ Read-only: `whoami`, `list_agents`, `get_agent`, `get_workflow`, `get_workflow_nodes`,
103
+ `list_knowledge`, `get_ingestion_status`.
104
+
105
+ Mutating: `create_agent`, `update_agent`, `create_workflow`, `update_workflow`,
106
+ `update_workflow_nodes`, `add_knowledge_url`, `link_knowledge`, `unlink_knowledge`.
107
+
108
+ ## Tests
109
+
110
+ ```bash
111
+ pip install -e ".[test]"
112
+ pytest tests/
113
+ ```
@@ -0,0 +1,17 @@
1
+ chattermate_cli/__init__.py,sha256=Fl2jq4a5tuyCsxms3gKGE8P9vEGyAXUmw_N7Xqlygw8,724
2
+ chattermate_cli/client.py,sha256=q48K6n9snPqzwcRE958noctenI8y62lhc3q4bJmd29A,10604
3
+ chattermate_cli/config.py,sha256=zagLyijC4HmuwCsEhz45X3Tf5N5PGanc18k0em9SO8I,3360
4
+ chattermate_cli/context.py,sha256=zK27rN9-PH_NTkxbOw1VgTQisLmqw73ppOCgoueykR0,2479
5
+ chattermate_cli/main.py,sha256=JFoRDdPzjGifFgzOxaPleciDnvwWbFwlG3nJ85HDm3g,2107
6
+ chattermate_cli/mcp_server.py,sha256=62m9QQBQAzQCHx5O-vYD8wABwMYfr9I114h68gcRVSU,8051
7
+ chattermate_cli/commands/__init__.py,sha256=I0IyYLYZl-b8dcdVpjaOYQ2lVNYVFov-4eTaCW4jEEQ,700
8
+ chattermate_cli/commands/agent.py,sha256=R81d6XJK0ooAMo4lk15fWgEJSMjZ8LzO4EANTX98gEI,4634
9
+ chattermate_cli/commands/auth.py,sha256=PiNAEM_0IEXa6puF95NxfXA850ZtVX-y6QJFRtIhuw8,7966
10
+ chattermate_cli/commands/knowledge.py,sha256=q10EBzoK0IhqVMQM1swXKHvHr1lxquip9rmpBOs0DdY,4002
11
+ chattermate_cli/commands/workflow.py,sha256=kwyf6Dc8IBFqUC4sQ3lg4WRIifpaW7wcHDMfCpxXTT4,4859
12
+ chattermate_cli-0.2.0.dist-info/licenses/LICENSE,sha256=60gz0wEB0a2wZOoendAWGBrLzAM7jvKAAKdOOm4WEdg,820
13
+ chattermate_cli-0.2.0.dist-info/METADATA,sha256=cqFVk-EV6KD_UgXHhXWDVUSMZocEYR-DkhrMIVQMz40,3520
14
+ chattermate_cli-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ chattermate_cli-0.2.0.dist-info/entry_points.txt,sha256=HYy6P-pExh28Y5vXbJ6PrQTG8iwZZ2oKX-qZWyugXBc,142
16
+ chattermate_cli-0.2.0.dist-info/top_level.txt,sha256=kDj5LfQJr-s1ce222gYWMuQ0kFdxsN241ZmXWdJZw28,16
17
+ chattermate_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ chattermate = chattermate_cli.main:main
3
+ chattermate-mcp = chattermate_cli.mcp_server:main
4
+ cmate = chattermate_cli.main:main
@@ -0,0 +1,18 @@
1
+ ChatterMate CLI (chattermate-cli)
2
+ Copyright (C) 2024 ChatterMate
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ The full text of the GNU Affero General Public License v3.0 is available at:
18
+ https://www.gnu.org/licenses/agpl-3.0.txt
@@ -0,0 +1 @@
1
+ chattermate_cli