systematics-mcp 1.0.0 → 1.0.2

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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Claude Code MCP server for the [Systematics](https://app.dovito.com) platform by
6
6
 
7
7
  ### 1. Add to Claude Code
8
8
 
9
- Add this to your `~/.claude/settings.json`:
9
+ Add this to your project or global `.mcp.json` file:
10
10
 
11
11
  ```json
12
12
  {
@@ -21,12 +21,14 @@ Add this to your `~/.claude/settings.json`:
21
21
 
22
22
  ### 2. Authenticate
23
23
 
24
- Start a Claude Code session. The first time you use a Systematics tool, your browser will open to sign in. Click **Authorize Claude Code** and you're connected. Your token is saved locally at `~/.systematics/token` and reused automatically for 90 days.
24
+ Restart Claude Code, then use `/mcp` to see Systematics listed. The first time you use a Systematics tool, your browser will open to sign in. Click **Authorize Claude Code** and you're connected. Your token is saved locally at `~/.systematics/token` and reused automatically for 90 days.
25
25
 
26
26
  That's it. No API keys, no repo access, no manual setup.
27
27
 
28
28
  ## How It Works
29
29
 
30
+ - The MCP server starts silently -- no browser popup on launch
31
+ - Authentication only triggers when you actually use a Systematics tool
30
32
  - You sign in with your normal Systematics account (Google or email)
31
33
  - Claude Code gets the same permissions as your account
32
34
  - Clients see only their business data
@@ -61,9 +63,32 @@ That's it. No API keys, no repo access, no manual setup.
61
63
  | `SYSTEMATICS_TOKEN` | - | Skip browser auth by providing a token directly |
62
64
  | `DOVITO_APP_URL` | `https://app.dovito.com` | Custom app URL (for self-hosted instances) |
63
65
 
66
+ ## Development
67
+
68
+ The MCP server lives in `mcp-server/` inside the [app.dovito.com](https://github.com/dovito-dev/app.dovito.com) repository.
69
+
70
+ ```bash
71
+ cd mcp-server
72
+ npm install
73
+ npm run dev # Run locally with tsx
74
+ npm run build # Compile TypeScript
75
+ ```
76
+
77
+ ### CI/CD
78
+
79
+ Publishing to npm is automated via GitHub Actions. To release a new version:
80
+
81
+ 1. Make changes in `mcp-server/`
82
+ 2. Bump the version in `mcp-server/package.json`
83
+ 3. Push to `main`
84
+ 4. GitHub Actions builds and publishes to npm automatically
85
+
86
+ If you push without bumping the version, the workflow skips the publish step.
87
+
64
88
  ## Security
65
89
 
66
- - Tokens are hashed before storage in the database
67
- - Token file is stored with `0o600` permissions (owner-only)
90
+ - Tokens are hashed (HMAC-SHA256) before storage in the database
91
+ - Token file is stored with `0o600` permissions in a `0o700` directory
68
92
  - Auth callback uses POST (token never appears in URLs or browser history)
69
93
  - All API calls go through the same validation and rate limiting as the web UI
94
+ - SSRF prevention: `DOVITO_APP_URL` is validated against an allowlist before any network call
@@ -5,7 +5,8 @@
5
5
  export declare class ApiClient {
6
6
  private baseUrl;
7
7
  private token;
8
- constructor(baseUrl: string, token: string);
8
+ private onAuthError?;
9
+ constructor(baseUrl: string, token: string, onAuthError?: () => void);
9
10
  private request;
10
11
  get<T>(path: string): Promise<T>;
11
12
  post<T>(path: string, body: unknown): Promise<T>;
@@ -5,9 +5,11 @@
5
5
  export class ApiClient {
6
6
  baseUrl;
7
7
  token;
8
- constructor(baseUrl, token) {
8
+ onAuthError;
9
+ constructor(baseUrl, token, onAuthError) {
9
10
  this.baseUrl = baseUrl.replace(/\/$/, "");
10
11
  this.token = token;
12
+ this.onAuthError = onAuthError;
11
13
  }
12
14
  async request(method, path, body) {
13
15
  const url = `${this.baseUrl}${path}`;
@@ -21,6 +23,9 @@ export class ApiClient {
21
23
  body: body ? JSON.stringify(body) : undefined,
22
24
  });
23
25
  if (!res.ok) {
26
+ if (res.status === 401 && this.onAuthError) {
27
+ this.onAuthError();
28
+ }
24
29
  const text = await res.text();
25
30
  let msg;
26
31
  try {
@@ -29,7 +34,6 @@ export class ApiClient {
29
34
  catch {
30
35
  msg = text;
31
36
  }
32
- // Truncate error message to avoid leaking internal details
33
37
  const safeMsg = msg.length > 500 ? msg.slice(0, 500) + "..." : msg;
34
38
  throw new Error(`API ${method} ${path} returned ${res.status}: ${safeMsg}`);
35
39
  }
package/dist/auth.d.ts CHANGED
@@ -2,6 +2,10 @@
2
2
  * Get the stored personal access token, or null if not found/expired.
3
3
  */
4
4
  export declare function getStoredToken(): string | null;
5
+ /**
6
+ * Delete the stored token (e.g. on 401 so re-auth triggers next time).
7
+ */
8
+ export declare function clearStoredToken(): void;
5
9
  /**
6
10
  * Run the browser-based authentication flow:
7
11
  * 1. Start a temporary local HTTP server
package/dist/auth.js CHANGED
@@ -21,6 +21,17 @@ export function getStoredToken() {
21
21
  return null;
22
22
  }
23
23
  }
24
+ /**
25
+ * Delete the stored token (e.g. on 401 so re-auth triggers next time).
26
+ */
27
+ export function clearStoredToken() {
28
+ try {
29
+ if (existsSync(TOKEN_FILE)) {
30
+ writeFileSync(TOKEN_FILE, "", { mode: 0o600 });
31
+ }
32
+ }
33
+ catch { /* ignore */ }
34
+ }
24
35
  /**
25
36
  * Save a token to the local config directory.
26
37
  */
@@ -100,7 +111,7 @@ export async function authenticateViaBrowser(appUrl) {
100
111
  <div class="card">
101
112
  <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg></div>
102
113
  <h1>Connected to Systematics</h1>
103
- <p>You can close this tab and return to Claude Code.</p>
114
+ <p>You can close this tab and return to your application.</p>
104
115
  </div>
105
116
  </body>
106
117
  </html>`);
@@ -126,7 +137,8 @@ export async function authenticateViaBrowser(appUrl) {
126
137
  return;
127
138
  }
128
139
  const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
129
- const authUrl = `${appUrl}/auth/mcp?callback=${encodeURIComponent(callbackUrl)}`;
140
+ const clientName = process.env.MCP_CLIENT_NAME || "Claude Code";
141
+ const authUrl = `${appUrl}/auth/mcp?callback=${encodeURIComponent(callbackUrl)}&client=${encodeURIComponent(clientName)}`;
130
142
  // Write to stderr so Claude Code can show it (stdout is MCP protocol)
131
143
  process.stderr.write(`\nOpening browser to authenticate with Systematics...\n`);
132
144
  process.stderr.write(`If the browser doesn't open, visit: ${authUrl}\n\n`);
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { ApiClient } from "./api-client.js";
6
- import { getStoredToken, authenticateViaBrowser } from "./auth.js";
6
+ import { getStoredToken, authenticateViaBrowser, clearStoredToken } from "./auth.js";
7
7
  // Validate DOVITO_APP_URL against allowed hostnames to prevent SSRF
8
8
  const rawUrl = process.env.DOVITO_APP_URL || "https://app.dovito.com";
9
9
  const parsedUrl = new URL(rawUrl);
@@ -13,32 +13,43 @@ if (!ALLOWED_HOSTS.includes(parsedUrl.hostname)) {
13
13
  process.exit(1);
14
14
  }
15
15
  const BASE_URL = parsedUrl.origin;
16
- // Resolve token: env var > stored token > browser auth flow
17
- async function resolveToken() {
18
- // 1. Explicit env var (highest priority)
19
- const envToken = process.env.SYSTEMATICS_TOKEN || process.env.MCP_API_KEY;
20
- if (envToken)
21
- return envToken;
22
- // 2. Previously stored token from browser auth
23
- const stored = getStoredToken();
24
- if (stored)
25
- return stored;
26
- // 3. Run browser auth flow
27
- return authenticateViaBrowser(BASE_URL);
16
+ // Resolve token silently (env var or stored file only -- no browser popup)
17
+ function getToken() {
18
+ return process.env.SYSTEMATICS_TOKEN || process.env.MCP_API_KEY || getStoredToken();
19
+ }
20
+ // On 401, clear stored token so next call re-authenticates
21
+ function handleAuthError() {
22
+ clearStoredToken();
23
+ api = null;
24
+ }
25
+ // Lazy API client -- initialized on first use or after auth
26
+ let api = null;
27
+ const token = getToken();
28
+ if (token) {
29
+ api = new ApiClient(BASE_URL, token, handleAuthError);
30
+ }
31
+ /**
32
+ * Get the API client, triggering browser auth if not yet authenticated.
33
+ * Returns the client or throws with an auth-required message.
34
+ */
35
+ async function getApi() {
36
+ if (api)
37
+ return api;
38
+ const newToken = await authenticateViaBrowser(BASE_URL);
39
+ api = new ApiClient(BASE_URL, newToken, handleAuthError);
40
+ return api;
28
41
  }
29
- const token = await resolveToken();
30
- const api = new ApiClient(BASE_URL, token);
31
42
  const server = new McpServer({
32
43
  name: "dovito",
33
44
  version: "1.0.0",
34
45
  });
35
46
  // ── Businesses ─────────────────────────────────────────────────────────────
36
47
  server.tool("list_businesses", "List all businesses (clients) in the Dovito portal", {}, async () => {
37
- const data = await api.get("/api/businesses");
48
+ const data = await (await getApi()).get("/api/businesses");
38
49
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
39
50
  });
40
51
  server.tool("get_business", "Get a single business by ID", { businessId: z.string().describe("Business UUID") }, async ({ businessId }) => {
41
- const data = await api.get(`/api/businesses/${businessId}`);
52
+ const data = await (await getApi()).get(`/api/businesses/${businessId}`);
42
53
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
43
54
  });
44
55
  server.tool("create_business", "Create a new business (client company)", {
@@ -51,7 +62,7 @@ server.tool("create_business", "Create a new business (client company)", {
51
62
  status: z.enum(["pending", "approved", "active"]).optional(),
52
63
  ownerUserId: z.string().optional().describe("User ID of the business owner, or 'none'"),
53
64
  }, async (params) => {
54
- const data = await api.post("/api/businesses", params);
65
+ const data = await (await getApi()).post("/api/businesses", params);
55
66
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
56
67
  });
57
68
  server.tool("update_business", "Update an existing business", {
@@ -66,16 +77,16 @@ server.tool("update_business", "Update an existing business", {
66
77
  status: z.enum(["pending", "approved", "active"]).optional(),
67
78
  assignedAmId: z.string().nullable().optional().describe("Account manager user ID"),
68
79
  }, async ({ businessId, ...updates }) => {
69
- const data = await api.patch(`/api/businesses/${businessId}`, updates);
80
+ const data = await (await getApi()).patch(`/api/businesses/${businessId}`, updates);
70
81
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
71
82
  });
72
83
  // ── Projects ───────────────────────────────────────────────────────────────
73
84
  server.tool("list_projects", "List all projects across businesses", {}, async () => {
74
- const data = await api.get("/api/projects");
85
+ const data = await (await getApi()).get("/api/projects");
75
86
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
76
87
  });
77
88
  server.tool("get_project", "Get a single project by ID", { projectId: z.string().describe("Project UUID") }, async ({ projectId }) => {
78
- const data = await api.get(`/api/projects/${projectId}`);
89
+ const data = await (await getApi()).get(`/api/projects/${projectId}`);
79
90
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
80
91
  });
81
92
  server.tool("create_project", "Create a new project under a business", {
@@ -89,7 +100,7 @@ server.tool("create_project", "Create a new project under a business", {
89
100
  startDate: z.string().optional().describe("ISO date"),
90
101
  estimatedEndDate: z.string().optional().describe("ISO date"),
91
102
  }, async (params) => {
92
- const data = await api.post("/api/projects", params);
103
+ const data = await (await getApi()).post("/api/projects", params);
93
104
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
94
105
  });
95
106
  server.tool("update_project", "Update an existing project", {
@@ -102,7 +113,7 @@ server.tool("update_project", "Update an existing project", {
102
113
  startDate: z.string().optional().describe("ISO date"),
103
114
  estimatedEndDate: z.string().optional().describe("ISO date"),
104
115
  }, async ({ projectId, ...updates }) => {
105
- const data = await api.patch(`/api/projects/${projectId}`, updates);
116
+ const data = await (await getApi()).patch(`/api/projects/${projectId}`, updates);
106
117
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
107
118
  });
108
119
  // ── Tasks ──────────────────────────────────────────────────────────────────
@@ -113,11 +124,11 @@ server.tool("list_tasks", "List all tasks, optionally filtered by business", {
113
124
  if (businessId)
114
125
  params.set("businessId", businessId);
115
126
  const query = params.toString() ? `?${params}` : "";
116
- const data = await api.get(`/api/tasks${query}`);
127
+ const data = await (await getApi()).get(`/api/tasks${query}`);
117
128
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
118
129
  });
119
130
  server.tool("list_project_tasks", "List tasks for a specific project", { projectId: z.string().describe("Project UUID") }, async ({ projectId }) => {
120
- const data = await api.get(`/api/projects/${projectId}/tasks`);
131
+ const data = await (await getApi()).get(`/api/projects/${projectId}/tasks`);
121
132
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
122
133
  });
123
134
  server.tool("create_task", "Create a task under a project", {
@@ -129,7 +140,7 @@ server.tool("create_task", "Create a task under a project", {
129
140
  startDate: z.string().optional().describe("ISO date"),
130
141
  dueDate: z.string().optional().describe("ISO date"),
131
142
  }, async ({ projectId, ...body }) => {
132
- const data = await api.post(`/api/projects/${projectId}/tasks`, body);
143
+ const data = await (await getApi()).post(`/api/projects/${projectId}/tasks`, body);
133
144
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
134
145
  });
135
146
  server.tool("update_task", "Update an existing task", {
@@ -143,7 +154,7 @@ server.tool("update_task", "Update an existing task", {
143
154
  startDate: z.string().optional().describe("ISO date"),
144
155
  dueDate: z.string().optional().describe("ISO date"),
145
156
  }, async ({ projectId, taskId, ...updates }) => {
146
- const data = await api.patch(`/api/projects/${projectId}/tasks/${taskId}`, updates);
157
+ const data = await (await getApi()).patch(`/api/projects/${projectId}/tasks/${taskId}`, updates);
147
158
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
148
159
  });
149
160
  server.tool("delete_task", "Delete a task (set confirm=true to proceed)", {
@@ -151,13 +162,13 @@ server.tool("delete_task", "Delete a task (set confirm=true to proceed)", {
151
162
  taskId: z.string().describe("Task UUID"),
152
163
  confirm: z.literal(true).describe("Must be true to confirm deletion"),
153
164
  }, async ({ projectId, taskId }) => {
154
- await api.delete(`/api/projects/${projectId}/tasks/${taskId}`);
165
+ await (await getApi()).delete(`/api/projects/${projectId}/tasks/${taskId}`);
155
166
  return { content: [{ type: "text", text: "Task deleted." }] };
156
167
  });
157
168
  // ── Catalog ────────────────────────────────────────────────────────────────
158
169
  server.tool("list_catalog_items", "List all items in the line item catalog", { all: z.boolean().optional().describe("Include inactive items") }, async ({ all }) => {
159
170
  const query = all ? "?all=true" : "";
160
- const data = await api.get(`/api/catalog${query}`);
171
+ const data = await (await getApi()).get(`/api/catalog${query}`);
161
172
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
162
173
  });
163
174
  server.tool("create_catalog_item", "Add an item to the line item catalog", {
@@ -168,7 +179,7 @@ server.tool("create_catalog_item", "Add an item to the line item catalog", {
168
179
  unitAmount: z.number().int().min(0).describe("Price in cents"),
169
180
  unit: z.string().optional().describe("hour, each, month, project, per seat, quarterly, annual"),
170
181
  }, async (params) => {
171
- const data = await api.post("/api/catalog", params);
182
+ const data = await (await getApi()).post("/api/catalog", params);
172
183
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
173
184
  });
174
185
  server.tool("update_catalog_item", "Update a catalog item", {
@@ -181,7 +192,7 @@ server.tool("update_catalog_item", "Update a catalog item", {
181
192
  unit: z.string().optional(),
182
193
  isActive: z.boolean().optional(),
183
194
  }, async ({ itemId, ...updates }) => {
184
- const data = await api.patch(`/api/catalog/${itemId}`, updates);
195
+ const data = await (await getApi()).patch(`/api/catalog/${itemId}`, updates);
185
196
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
186
197
  });
187
198
  // ── Users ──────────────────────────────────────────────────────────────────
@@ -195,12 +206,12 @@ server.tool("list_users", "List all users in the portal", {
195
206
  if (includeStaff)
196
207
  params.set("includeStaff", "true");
197
208
  const query = params.toString() ? `?${params}` : "";
198
- const data = await api.get(`/api/admin/users${query}`);
209
+ const data = await (await getApi()).get(`/api/admin/users${query}`);
199
210
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
200
211
  });
201
212
  // ── Proposals ──────────────────────────────────────────────────────────────
202
213
  server.tool("list_proposals", "List proposals for a project", { projectId: z.string().describe("Project UUID") }, async ({ projectId }) => {
203
- const data = await api.get(`/api/projects/${projectId}/proposals`);
214
+ const data = await (await getApi()).get(`/api/projects/${projectId}/proposals`);
204
215
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
205
216
  });
206
217
  server.tool("create_proposal", "Create a proposal (quote) for a project", {
@@ -214,12 +225,12 @@ server.tool("create_proposal", "Create a proposal (quote) for a project", {
214
225
  })).describe("Line items for the proposal"),
215
226
  expiresAt: z.string().optional().describe("ISO date for expiration"),
216
227
  }, async ({ projectId, ...body }) => {
217
- const data = await api.post(`/api/projects/${projectId}/proposals`, body);
228
+ const data = await (await getApi()).post(`/api/projects/${projectId}/proposals`, body);
218
229
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
219
230
  });
220
231
  // ── Conversations & Messages ───────────────────────────────────────────────
221
232
  server.tool("list_conversations", "List all conversations (message threads) for the service account", {}, async () => {
222
- const data = await api.get("/api/messages");
233
+ const data = await (await getApi()).get("/api/messages");
223
234
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
224
235
  });
225
236
  server.tool("get_conversation_messages", "Get messages in a conversation thread", {
@@ -230,7 +241,7 @@ server.tool("get_conversation_messages", "Get messages in a conversation thread"
230
241
  if (cursor)
231
242
  params.set("cursor", cursor);
232
243
  const query = params.toString() ? `?${params}` : "";
233
- const data = await api.get(`/api/messages/${conversationId}${query}`);
244
+ const data = await (await getApi()).get(`/api/messages/${conversationId}${query}`);
234
245
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
235
246
  });
236
247
  server.tool("create_conversation", "Create a new conversation (DM, group, project, or business thread)", {
@@ -240,7 +251,7 @@ server.tool("create_conversation", "Create a new conversation (DM, group, projec
240
251
  projectId: z.string().optional().describe("Project UUID (for project type)"),
241
252
  businessId: z.string().optional().describe("Business UUID (for business type)"),
242
253
  }, async (params) => {
243
- const data = await api.post("/api/conversations", params);
254
+ const data = await (await getApi()).post("/api/conversations", params);
244
255
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
245
256
  });
246
257
  server.tool("send_message", "Send a message in a conversation", {
@@ -248,12 +259,12 @@ server.tool("send_message", "Send a message in a conversation", {
248
259
  content: z.string().describe("Message content (1-10000 chars)"),
249
260
  parentMessageId: z.string().optional().describe("Reply to a specific message"),
250
261
  }, async ({ conversationId, ...body }) => {
251
- const data = await api.post(`/api/messages/${conversationId}`, body);
262
+ const data = await (await getApi()).post(`/api/messages/${conversationId}`, body);
252
263
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
253
264
  });
254
265
  // ── Deliverables ───────────────────────────────────────────────────────────
255
266
  server.tool("list_deliverables", "List deliverables for a project", { projectId: z.string().describe("Project UUID") }, async ({ projectId }) => {
256
- const data = await api.get(`/api/projects/${projectId}/deliverables`);
267
+ const data = await (await getApi()).get(`/api/projects/${projectId}/deliverables`);
257
268
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
258
269
  });
259
270
  server.tool("create_deliverable", "Create a deliverable under a project", {
@@ -262,7 +273,7 @@ server.tool("create_deliverable", "Create a deliverable under a project", {
262
273
  description: z.string().optional(),
263
274
  status: z.enum(["pending", "in_progress", "completed"]).optional(),
264
275
  }, async ({ projectId, ...body }) => {
265
- const data = await api.post(`/api/projects/${projectId}/deliverables`, body);
276
+ const data = await (await getApi()).post(`/api/projects/${projectId}/deliverables`, body);
266
277
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
267
278
  });
268
279
  server.tool("update_deliverable", "Update a deliverable", {
@@ -272,12 +283,12 @@ server.tool("update_deliverable", "Update a deliverable", {
272
283
  description: z.string().optional(),
273
284
  status: z.enum(["pending", "in_progress", "completed"]).optional(),
274
285
  }, async ({ projectId, deliverableId, ...updates }) => {
275
- const data = await api.patch(`/api/projects/${projectId}/deliverables/${deliverableId}`, updates);
286
+ const data = await (await getApi()).patch(`/api/projects/${projectId}/deliverables/${deliverableId}`, updates);
276
287
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
277
288
  });
278
289
  // ── Applications (Pipeline) ───────────────────────────────────────────────
279
290
  server.tool("list_applications", "List all applications in the pipeline", {}, async () => {
280
- const data = await api.get("/api/admin/applications");
291
+ const data = await (await getApi()).get("/api/admin/applications");
281
292
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
282
293
  });
283
294
  // ── Start server ───────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "systematics-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "Claude Code MCP server for the Systematics platform by Dovito Business Solutions",
6
6
  "bin": {