jetty-mcp-server 1.0.0 → 1.0.3
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 +2 -3
- package/dist/client.d.ts +17 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +126 -40
- package/dist/client.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +232 -4
- package/dist/tools.js.map +1 -1
- package/package.json +3 -1
- package/src/client.ts +168 -42
- package/src/tools.ts +377 -14
- package/tests/auth.test.mjs +129 -0
- package/tests/routines.test.mjs +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Smoke tests for routine client methods.
|
|
2
|
+
// Mocks global fetch and asserts each method hits the expected URL,
|
|
3
|
+
// HTTP method, body, and Authorization: Bearer header.
|
|
4
|
+
//
|
|
5
|
+
// Imports from ../dist (so the project must be built before running).
|
|
6
|
+
// Use `npm test` which runs `npm run build` via the `pretest` hook.
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { JettyClient } from "../dist/client.js";
|
|
14
|
+
|
|
15
|
+
const TOKEN = "test-token-xyz";
|
|
16
|
+
const API_URL = "https://flows-api.test.jetty.io";
|
|
17
|
+
|
|
18
|
+
function setEnv() {
|
|
19
|
+
process.env.JETTY_API_TOKEN = TOKEN;
|
|
20
|
+
process.env.JETTY_API_URL = API_URL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Install a fetch mock that records call args and returns a JSON body.
|
|
25
|
+
* Returns { calls, restore } — calls is an array of { url, init }.
|
|
26
|
+
*/
|
|
27
|
+
function mockFetch(responseBody = {}) {
|
|
28
|
+
const calls = [];
|
|
29
|
+
const original = globalThis.fetch;
|
|
30
|
+
globalThis.fetch = async (url, init) => {
|
|
31
|
+
calls.push({ url: String(url), init: init || {} });
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
status: 200,
|
|
35
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
36
|
+
async json() {
|
|
37
|
+
return responseBody;
|
|
38
|
+
},
|
|
39
|
+
async text() {
|
|
40
|
+
return JSON.stringify(responseBody);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
calls,
|
|
46
|
+
restore: () => {
|
|
47
|
+
globalThis.fetch = original;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function authHeader(init) {
|
|
53
|
+
const h = init.headers || {};
|
|
54
|
+
// headers may be a plain object (current implementation passes a Record)
|
|
55
|
+
return h.Authorization || h.authorization;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function bodyJson(init) {
|
|
59
|
+
if (!init.body) return undefined;
|
|
60
|
+
return JSON.parse(init.body);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("listRoutines without task hits collection-wide endpoint", async () => {
|
|
64
|
+
setEnv();
|
|
65
|
+
const { calls, restore } = mockFetch({ routines: [] });
|
|
66
|
+
try {
|
|
67
|
+
const c = new JettyClient();
|
|
68
|
+
await c.listRoutines("my-coll");
|
|
69
|
+
assert.equal(calls.length, 1);
|
|
70
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/my-coll`);
|
|
71
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
72
|
+
} finally {
|
|
73
|
+
restore();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("listRoutines with task hits task-scoped endpoint", async () => {
|
|
78
|
+
setEnv();
|
|
79
|
+
const { calls, restore } = mockFetch({ routines: [] });
|
|
80
|
+
try {
|
|
81
|
+
const c = new JettyClient();
|
|
82
|
+
await c.listRoutines("my-coll", "my-task");
|
|
83
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/my-coll/my-task`);
|
|
84
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
85
|
+
} finally {
|
|
86
|
+
restore();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("getRoutine hits the named routine endpoint", async () => {
|
|
91
|
+
setEnv();
|
|
92
|
+
const { calls, restore } = mockFetch({ id: 1 });
|
|
93
|
+
try {
|
|
94
|
+
const c = new JettyClient();
|
|
95
|
+
await c.getRoutine("c", "t", "r");
|
|
96
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r`);
|
|
97
|
+
assert.equal(calls[0].init.method, undefined); // GET (default)
|
|
98
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
99
|
+
} finally {
|
|
100
|
+
restore();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("createRoutine POSTs JSON body to task endpoint", async () => {
|
|
105
|
+
setEnv();
|
|
106
|
+
const { calls, restore } = mockFetch({ id: 42 });
|
|
107
|
+
try {
|
|
108
|
+
const c = new JettyClient();
|
|
109
|
+
const body = {
|
|
110
|
+
name: "daily-summary",
|
|
111
|
+
cadence: { type: "daily", hour_utc: 9, minute_utc: 0 },
|
|
112
|
+
init_params_overrides: { prompt: "summarize" },
|
|
113
|
+
};
|
|
114
|
+
await c.createRoutine("c", "t", body);
|
|
115
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t`);
|
|
116
|
+
assert.equal(calls[0].init.method, "POST");
|
|
117
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
118
|
+
assert.equal(calls[0].init.headers["Content-Type"], "application/json");
|
|
119
|
+
assert.deepEqual(bodyJson(calls[0].init), body);
|
|
120
|
+
} finally {
|
|
121
|
+
restore();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("updateRoutine PATCHes the named routine endpoint", async () => {
|
|
126
|
+
setEnv();
|
|
127
|
+
const { calls, restore } = mockFetch({ id: 42 });
|
|
128
|
+
try {
|
|
129
|
+
const c = new JettyClient();
|
|
130
|
+
const patch = { paused: true };
|
|
131
|
+
await c.updateRoutine("c", "t", "r", patch);
|
|
132
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r`);
|
|
133
|
+
assert.equal(calls[0].init.method, "PATCH");
|
|
134
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
135
|
+
assert.deepEqual(bodyJson(calls[0].init), patch);
|
|
136
|
+
} finally {
|
|
137
|
+
restore();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("deleteRoutine DELETEs the named routine endpoint", async () => {
|
|
142
|
+
setEnv();
|
|
143
|
+
const { calls, restore } = mockFetch({ ok: true });
|
|
144
|
+
try {
|
|
145
|
+
const c = new JettyClient();
|
|
146
|
+
await c.deleteRoutine("c", "t", "r");
|
|
147
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r`);
|
|
148
|
+
assert.equal(calls[0].init.method, "DELETE");
|
|
149
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
150
|
+
} finally {
|
|
151
|
+
restore();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("pauseRoutine POSTs to /pause", async () => {
|
|
156
|
+
setEnv();
|
|
157
|
+
const { calls, restore } = mockFetch({ ok: true });
|
|
158
|
+
try {
|
|
159
|
+
const c = new JettyClient();
|
|
160
|
+
await c.pauseRoutine("c", "t", "r");
|
|
161
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r/pause`);
|
|
162
|
+
assert.equal(calls[0].init.method, "POST");
|
|
163
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
164
|
+
} finally {
|
|
165
|
+
restore();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("resumeRoutine POSTs to /resume", async () => {
|
|
170
|
+
setEnv();
|
|
171
|
+
const { calls, restore } = mockFetch({ ok: true });
|
|
172
|
+
try {
|
|
173
|
+
const c = new JettyClient();
|
|
174
|
+
await c.resumeRoutine("c", "t", "r");
|
|
175
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r/resume`);
|
|
176
|
+
assert.equal(calls[0].init.method, "POST");
|
|
177
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
178
|
+
} finally {
|
|
179
|
+
restore();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("runRoutineNow POSTs to /run-now", async () => {
|
|
184
|
+
setEnv();
|
|
185
|
+
const { calls, restore } = mockFetch({ workflow_id: "wf-123" });
|
|
186
|
+
try {
|
|
187
|
+
const c = new JettyClient();
|
|
188
|
+
const res = await c.runRoutineNow("c", "t", "r");
|
|
189
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r/run-now`);
|
|
190
|
+
assert.equal(calls[0].init.method, "POST");
|
|
191
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
192
|
+
assert.equal(res.workflow_id, "wf-123");
|
|
193
|
+
} finally {
|
|
194
|
+
restore();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("listRoutineRuns hits /runs and includes ?limit when provided", async () => {
|
|
199
|
+
setEnv();
|
|
200
|
+
const { calls, restore } = mockFetch({ trajectories: [] });
|
|
201
|
+
try {
|
|
202
|
+
const c = new JettyClient();
|
|
203
|
+
await c.listRoutineRuns("c", "t", "r");
|
|
204
|
+
assert.equal(calls[0].url, `${API_URL}/api/v1/routines/c/t/r/runs`);
|
|
205
|
+
assert.equal(authHeader(calls[0].init), `Bearer ${TOKEN}`);
|
|
206
|
+
|
|
207
|
+
await c.listRoutineRuns("c", "t", "r", 25);
|
|
208
|
+
assert.equal(
|
|
209
|
+
calls[1].url,
|
|
210
|
+
`${API_URL}/api/v1/routines/c/t/r/runs?limit=25`
|
|
211
|
+
);
|
|
212
|
+
} finally {
|
|
213
|
+
restore();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("missing JETTY_API_TOKEN raises a helpful error", async () => {
|
|
218
|
+
delete process.env.JETTY_API_TOKEN;
|
|
219
|
+
process.env.JETTY_API_URL = API_URL;
|
|
220
|
+
// Point HOME at an empty dir so the ~/.config/jetty/token fallback can't fire
|
|
221
|
+
// (the dev machine running these tests may have a real token on disk).
|
|
222
|
+
const savedHome = process.env.HOME;
|
|
223
|
+
const home = mkdtempSync(join(tmpdir(), "jetty-notoken-"));
|
|
224
|
+
process.env.HOME = home;
|
|
225
|
+
try {
|
|
226
|
+
const c = new JettyClient();
|
|
227
|
+
await assert.rejects(
|
|
228
|
+
() => c.listRoutines("c"),
|
|
229
|
+
/No Jetty token found.*jetty login/s
|
|
230
|
+
);
|
|
231
|
+
} finally {
|
|
232
|
+
if (savedHome === undefined) delete process.env.HOME;
|
|
233
|
+
else process.env.HOME = savedHome;
|
|
234
|
+
rmSync(home, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
});
|