jetty-mcp-server 1.0.3 → 1.1.0

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/src/client.ts DELETED
@@ -1,332 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- const DEFAULT_API_URL = "https://flows-api.jetty.io";
6
-
7
- // The `jetty` CLI / Claude Code skill writes the collection-scoped `mlc_` key
8
- // here on `jetty login`. We fall back to it so an already-logged-in user does
9
- // not also have to plumb JETTY_API_TOKEN into the MCP server's env. Resolved
10
- // lazily (not memoized at module load) so HOME changes are honoured.
11
- function tokenFilePath(): string {
12
- return join(homedir(), ".config", "jetty", "token");
13
- }
14
-
15
- export class JettyClient {
16
- private token: string | undefined;
17
- private apiUrl: string;
18
-
19
- constructor() {
20
- this.token = resolveToken();
21
- this.apiUrl = process.env.JETTY_API_URL || DEFAULT_API_URL;
22
- }
23
-
24
- private requireToken(): string {
25
- if (!this.token) {
26
- throw new Error(
27
- "No Jetty token found. Set JETTY_API_TOKEN, or run `jetty login` " +
28
- `(writes ${tokenFilePath()}). ` +
29
- "Get a token at https://jetty.io → Settings → API Tokens"
30
- );
31
- }
32
- return this.token;
33
- }
34
-
35
- private async request(
36
- path: string,
37
- options: RequestInit = {}
38
- ): Promise<unknown> {
39
- const url = `${this.apiUrl}${path}`;
40
- const headers: Record<string, string> = {
41
- Authorization: `Bearer ${this.requireToken()}`,
42
- ...((options.headers as Record<string, string>) || {}),
43
- };
44
-
45
- const res = await fetch(url, { ...options, headers });
46
-
47
- if (!res.ok) {
48
- const body = await res.text();
49
- throw new Error(`Jetty API error ${res.status}: ${body}`);
50
- }
51
-
52
- const contentType = res.headers.get("content-type") || "";
53
- if (contentType.includes("application/json")) {
54
- return res.json();
55
- }
56
- return res.text();
57
- }
58
-
59
- private api(path: string, options?: RequestInit) {
60
- return this.request(path, options);
61
- }
62
-
63
- // Collections
64
- async listCollections() {
65
- return this.api("/api/v1/collections/");
66
- }
67
-
68
- async getCollection(collection: string) {
69
- return this.api(`/api/v1/collections/${collection}`);
70
- }
71
-
72
- // Tasks
73
- async listTasks(collection: string) {
74
- return this.api(`/api/v1/tasks/${collection}/`);
75
- }
76
-
77
- async getTask(collection: string, task: string) {
78
- return this.api(`/api/v1/tasks/${collection}/${task}`);
79
- }
80
-
81
- async createTask(
82
- collection: string,
83
- name: string,
84
- workflow: unknown,
85
- description?: string
86
- ) {
87
- return this.api(`/api/v1/tasks/${collection}`, {
88
- method: "POST",
89
- headers: { "Content-Type": "application/json" },
90
- body: JSON.stringify({ name, description: description || "", workflow }),
91
- });
92
- }
93
-
94
- async updateTask(
95
- collection: string,
96
- task: string,
97
- updates: { workflow?: unknown; description?: string }
98
- ) {
99
- return this.api(`/api/v1/tasks/${collection}/${task}`, {
100
- method: "PUT",
101
- headers: { "Content-Type": "application/json" },
102
- body: JSON.stringify(updates),
103
- });
104
- }
105
-
106
- async deleteTask(collection: string, task: string) {
107
- return this.api(`/api/v1/tasks/${collection}/${task}`, {
108
- method: "DELETE",
109
- });
110
- }
111
-
112
- // Trial keys
113
- async getTrialStatus(collection: string) {
114
- return this.api(`/api/v1/trial/${collection}`);
115
- }
116
-
117
- async activateTrial(collection: string) {
118
- return this.api(`/api/v1/trial/${collection}/activate`, {
119
- method: "POST",
120
- });
121
- }
122
-
123
- // Collection environment
124
- async getCollectionEnvironment(collection: string) {
125
- return this.api(`/api/v1/collections/${collection}/environment`);
126
- }
127
-
128
- // Run workflows
129
- async runWorkflow(
130
- collection: string,
131
- task: string,
132
- initParams?: Record<string, unknown>,
133
- useTrialKeys?: boolean
134
- ) {
135
- const formData = new FormData();
136
- formData.append("init_params", JSON.stringify(initParams || {}));
137
- if (useTrialKeys) {
138
- formData.append("use_trial_keys", "true");
139
- }
140
-
141
- return this.api(`/api/v1/run/${collection}/${task}`, {
142
- method: "POST",
143
- body: formData,
144
- });
145
- }
146
-
147
- async runWorkflowSync(
148
- collection: string,
149
- task: string,
150
- initParams?: Record<string, unknown>,
151
- useTrialKeys?: boolean
152
- ) {
153
- const formData = new FormData();
154
- formData.append("init_params", JSON.stringify(initParams || {}));
155
- if (useTrialKeys) {
156
- formData.append("use_trial_keys", "true");
157
- }
158
-
159
- return this.api(`/api/v1/run-sync/${collection}/${task}`, {
160
- method: "POST",
161
- body: formData,
162
- });
163
- }
164
-
165
- // Trajectories
166
- async listTrajectories(
167
- collection: string,
168
- task: string,
169
- limit = 10,
170
- page = 1
171
- ) {
172
- return this.api(
173
- `/api/v1/db/trajectories/${collection}/${task}?limit=${limit}&page=${page}`
174
- );
175
- }
176
-
177
- async getTrajectory(
178
- collection: string,
179
- task: string,
180
- trajectoryId: string
181
- ) {
182
- return this.api(
183
- `/api/v1/db/trajectory/${collection}/${task}/${trajectoryId}`
184
- );
185
- }
186
-
187
- // Stats
188
- async getStats(collection: string, task: string) {
189
- return this.api(`/api/v1/db/stats/${collection}/${task}`);
190
- }
191
-
192
- // Labels
193
- async addLabel(
194
- collection: string,
195
- task: string,
196
- trajectoryId: string,
197
- key: string,
198
- value: string,
199
- author: string
200
- ) {
201
- return this.api(
202
- `/api/v1/trajectory/${collection}/${task}/${trajectoryId}/labels`,
203
- {
204
- method: "POST",
205
- headers: { "Content-Type": "application/json" },
206
- body: JSON.stringify({ key, value, author }),
207
- }
208
- );
209
- }
210
-
211
- // Workflow logs
212
- async getWorkflowLogs(workflowId: string) {
213
- return this.api(`/api/v1/workflows-logs/${workflowId}`);
214
- }
215
-
216
- // Step templates
217
- async listStepTemplates() {
218
- return this.request("/api/v1/step-templates");
219
- }
220
-
221
- async getStepTemplate(name: string) {
222
- return this.request(`/api/v1/step-templates/${name}`);
223
- }
224
-
225
- // Environment variables
226
- async setEnvironmentVars(
227
- collection: string,
228
- vars: Record<string, string>
229
- ) {
230
- return this.api(`/api/v1/collections/${collection}/environment`, {
231
- method: "PATCH",
232
- headers: { "Content-Type": "application/json" },
233
- body: JSON.stringify({ environment_variables: vars }),
234
- });
235
- }
236
-
237
- // Routines (scheduled runs)
238
- async listRoutines(collection: string, task?: string) {
239
- const path = task
240
- ? `/api/v1/routines/${collection}/${task}`
241
- : `/api/v1/routines/${collection}`;
242
- return this.api(path);
243
- }
244
-
245
- async getRoutine(collection: string, task: string, name: string) {
246
- return this.api(`/api/v1/routines/${collection}/${task}/${name}`);
247
- }
248
-
249
- async createRoutine(
250
- collection: string,
251
- task: string,
252
- body: Record<string, unknown>
253
- ) {
254
- return this.api(`/api/v1/routines/${collection}/${task}`, {
255
- method: "POST",
256
- headers: { "Content-Type": "application/json" },
257
- body: JSON.stringify(body),
258
- });
259
- }
260
-
261
- async updateRoutine(
262
- collection: string,
263
- task: string,
264
- name: string,
265
- patch: Record<string, unknown>
266
- ) {
267
- return this.api(`/api/v1/routines/${collection}/${task}/${name}`, {
268
- method: "PATCH",
269
- headers: { "Content-Type": "application/json" },
270
- body: JSON.stringify(patch),
271
- });
272
- }
273
-
274
- async deleteRoutine(collection: string, task: string, name: string) {
275
- return this.api(`/api/v1/routines/${collection}/${task}/${name}`, {
276
- method: "DELETE",
277
- });
278
- }
279
-
280
- async pauseRoutine(collection: string, task: string, name: string) {
281
- return this.api(
282
- `/api/v1/routines/${collection}/${task}/${name}/pause`,
283
- { method: "POST" }
284
- );
285
- }
286
-
287
- async resumeRoutine(collection: string, task: string, name: string) {
288
- return this.api(
289
- `/api/v1/routines/${collection}/${task}/${name}/resume`,
290
- { method: "POST" }
291
- );
292
- }
293
-
294
- async runRoutineNow(collection: string, task: string, name: string) {
295
- return this.api(
296
- `/api/v1/routines/${collection}/${task}/${name}/run-now`,
297
- { method: "POST" }
298
- );
299
- }
300
-
301
- async listRoutineRuns(
302
- collection: string,
303
- task: string,
304
- name: string,
305
- limit?: number
306
- ) {
307
- const qs = typeof limit === "number" ? `?limit=${limit}` : "";
308
- return this.api(
309
- `/api/v1/routines/${collection}/${task}/${name}/runs${qs}`
310
- );
311
- }
312
- }
313
-
314
- /**
315
- * Resolve the API token: prefer JETTY_API_TOKEN (trimmed, non-empty), then the
316
- * token file written by `jetty login`. The env var is checked for emptiness
317
- * because the plugin's .mcp.json may pass it through as "" — which must not
318
- * count as "set" and must not block the file fallback.
319
- */
320
- function resolveToken(): string | undefined {
321
- const fromEnv = process.env.JETTY_API_TOKEN?.trim();
322
- if (fromEnv) return fromEnv;
323
-
324
- try {
325
- const fromFile = readFileSync(tokenFilePath(), "utf8").trim();
326
- if (fromFile) return fromFile;
327
- } catch {
328
- // file absent / unreadable — fall through to undefined
329
- }
330
-
331
- return undefined;
332
- }
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { JettyClient } from "./client.js";
6
- import { registerTools } from "./tools.js";
7
-
8
- const server = new McpServer({
9
- name: "jetty",
10
- version: "1.0.0",
11
- });
12
-
13
- const client = new JettyClient();
14
- registerTools(server, client);
15
-
16
- const transport = new StdioServerTransport();
17
- await server.connect(transport);