History

Clean up Elysia routes: deduplicate MCP handler, remove redundant validation

- Extract MCP handler to shared function instead of copy-pasting for 3 HTTP methods - Remove redundant manual workspaceId/body validation (Elysia schema already validates) - Simplify getBaseToolCount() to use baseTools.size - Fix .gitignore wildcard patterns (_.log -> *.log)

Rhys Sullivan committed 9 months ago

Commit 85da31c1 parent 3a401ab3 files · +29 −80Browse files

Unified · Split

3 files
  1. .gitignore +2 −2
  2. executor/apps/server/src/database.ts +10 −0
  3. executor/apps/server/src/index.ts +17 −78
.gitignore+2−2
@@ -12,8 +12,8 @@ coverage · 11 unmodified lines
1212
1313 # logs
1414 logs
15−_.log
16−report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
15+*.log
16+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
1717
1818 # dotenv environment variable files
1919 .env
executor/apps/server/src/database.ts+10−0
@@ -56,6 +56,16 @@ export class ExecutorDatabase { · 55 unmodified lines
5656 this.client = new ConvexHttpClient(convexUrl);
5757 }
5858
59+ // The `as never` casts below are intentional. ConvexHttpClient.mutation() and
60+ // .query() expect the exact generated function reference type and its
61+ // corresponding args type. Because we call them dynamically with string
62+ // function names (e.g. "database:createTask") and generic arg objects,
63+ // TypeScript cannot verify the relationship between name and args at the
64+ // call site. Properly typing this would require a discriminated-union
65+ // overload map for every Convex function, which adds significant
66+ // complexity for little safety gain — the real type safety lives in the
67+ // public method signatures of this class and the Convex handler
68+ // definitions themselves.
5969 private async mutation<TArgs extends Record<string, unknown>, TResult>(
6070 name: MutationName,
6171 args: TArgs,
executor/apps/server/src/index.ts+17−78
@@ -159,6 +159,14 @@ function parseMcpContext(url: URL): McpWorkspaceContext | undefined { · 158 unmodified lines
159159 return { workspaceId, actorId, clientId };
160160 }
161161
162+// ── MCP handler ──
163+
164+const handleMcp = async ({ request }: { request: Request }) => {
165+ const url = new URL(request.url);
166+ const context = parseMcpContext(url);
167+ return await handleMcpRequest(service, request, context);
168+};
169+
162170 // ── Elysia app ──
163171
164172 const app = new Elysia()
@@ -173,21 +181,9 @@ const app = new Elysia() · 8 unmodified lines
173181 // ── MCP (raw protocol passthrough) ──
174182 // Optional query params: ?workspaceId=...&actorId=... to bind workspace context.
175183 // When bound, run_code description includes sandbox tool inventory and input is simplified.
176− .post("/mcp", async ({ request }) => {
177− const url = new URL(request.url);
178− const context = parseMcpContext(url);
179− return await handleMcpRequest(service, request, context);
180− })
181− .get("/mcp", async ({ request }) => {
182− const url = new URL(request.url);
183− const context = parseMcpContext(url);
184− return await handleMcpRequest(service, request, context);
185− })
186− .delete("/mcp", async ({ request }) => {
187− const url = new URL(request.url);
188− const context = parseMcpContext(url);
189− return await handleMcpRequest(service, request, context);
190− })
184+ .post("/mcp", handleMcp)
185+ .get("/mcp", handleMcp)
186+ .delete("/mcp", handleMcp)
191187
192188 // ── Health ──
193189 .get("/api/health", () => ({
@@ -208,11 +204,7 @@ const app = new Elysia() · 14 unmodified lines
208204 .get("/api/runtime-targets", () => service.listRuntimes())
209205
210206 // ── Tools ──
211− .get("/api/tools", async ({ query, set }) => {
212− if (!query.workspaceId) {
213− set.status = 400;
214− return { error: "workspaceId is required" };
215− }
207+ .get("/api/tools", async ({ query }) => {
216208 return await service.listTools({
217209 workspaceId: query.workspaceId,
218210 actorId: query.actorId,
@@ -227,11 +219,7 @@ const app = new Elysia() · 8 unmodified lines
227219 })
228220
229221 // ── Tool Sources ──
230− .get("/api/tool-sources", async ({ query, set }) => {
231− if (!query.workspaceId) {
232− set.status = 400;
233− return { error: "workspaceId is required" };
234− }
222+ .get("/api/tool-sources", async ({ query }) => {
235223 return await service.listToolSources(query.workspaceId);
236224 }, {
237225 query: t.Object({
@@ -240,10 +228,6 @@ const app = new Elysia() · 2 unmodified lines
240228 })
241229
242230 .post("/api/tool-sources", async ({ body, set }) => {
243− if (!body.workspaceId || !body.name || !body.type || !body.config) {
244− set.status = 400;
245− return { error: "workspaceId, name, type, and config are required" };
246− }
247231 try {
248232 return await service.upsertToolSource({
249233 id: body.id,
@@ -269,10 +253,6 @@ const app = new Elysia() · 19 unmodified lines
269253 })
270254
271255 .delete("/api/tool-sources/:sourceId", async ({ params, query, set }) => {
272− if (!query.workspaceId) {
273− set.status = 400;
274− return { error: "workspaceId is required" };
275− }
276256 const deleted = await service.deleteToolSource(query.workspaceId, params.sourceId);
277257 if (!deleted) {
278258 set.status = 404;
@@ -285,11 +265,7 @@ const app = new Elysia() · 6 unmodified lines
285265 })
286266
287267 // ── Tasks ──
288− .get("/api/tasks", async ({ query, set }) => {
289− if (!query.workspaceId) {
290− set.status = 400;
291− return { error: "workspaceId is required" };
292− }
268+ .get("/api/tasks", async ({ query }) => {
293269 return await service.listTasks(query.workspaceId);
294270 }, {
295271 query: t.Object({ workspaceId: t.String() }),
@@ -316,10 +292,6 @@ const app = new Elysia() · 20 unmodified lines
316292 })
317293
318294 .get("/api/tasks/:taskId", async ({ params, query, set }) => {
319− if (!query.workspaceId) {
320− set.status = 400;
321− return { error: "workspaceId is required" };
322− }
323295 const task = await service.getTask(params.taskId, query.workspaceId);
324296 if (!task) {
325297 set.status = 404;
@@ -332,10 +304,6 @@ const app = new Elysia() · 6 unmodified lines
332304 })
333305
334306 .get("/api/tasks/:taskId/events", async ({ params, query, set }) => {
335− if (!query.workspaceId) {
336− set.status = 400;
337− return { error: "workspaceId is required" };
338− }
339307 const task = await service.getTask(params.taskId, query.workspaceId);
340308 if (!task) {
341309 set.status = 404;
@@ -349,10 +317,6 @@ const app = new Elysia() · 7 unmodified lines
349317
350318 // ── Approvals ──
351319 .get("/api/approvals", async ({ query, set }) => {
352− if (!query.workspaceId) {
353− set.status = 400;
354− return { error: "workspaceId is required" };
355− }
356320 const status = query.status as ApprovalStatus | undefined;
357321
358322 if (status === "pending") {
@@ -373,11 +337,6 @@ const app = new Elysia() · 14 unmodified lines
373337 })
374338
375339 .post("/api/approvals/:approvalId", async ({ params, body, set }) => {
376− if (!body.workspaceId || (body.decision !== "approved" && body.decision !== "denied")) {
377− set.status = 400;
378− return { error: "workspaceId and decision are required" };
379− }
380−
381340 const resolved = await service.resolveApproval(
382341 body.workspaceId,
383342 params.approvalId,
@@ -402,21 +361,13 @@ const app = new Elysia() · 18 unmodified lines
402361 })
403362
404363 // ── Policies ──
405− .get("/api/policies", async ({ query, set }) => {
406− if (!query.workspaceId) {
407− set.status = 400;
408− return { error: "workspaceId is required" };
409− }
364+ .get("/api/policies", async ({ query }) => {
410365 return await service.listAccessPolicies(query.workspaceId);
411366 }, {
412367 query: t.Object({ workspaceId: t.String() }),
413368 })
414369
415− .post("/api/policies", async ({ body, set }) => {
416− if (!body.workspaceId || !body.toolPathPattern || !body.decision) {
417− set.status = 400;
418− return { error: "workspaceId, toolPathPattern, and decision are required" };
419− }
370+ .post("/api/policies", async ({ body }) => {
420371 return await service.upsertAccessPolicy({
421372 id: body.id,
422373 workspaceId: body.workspaceId,
@@ -439,21 +390,13 @@ const app = new Elysia() · 16 unmodified lines
439390 })
440391
441392 // ── Credentials ──
442− .get("/api/credentials", async ({ query, set }) => {
443− if (!query.workspaceId) {
444− set.status = 400;
445− return { error: "workspaceId is required" };
446− }
393+ .get("/api/credentials", async ({ query }) => {
447394 return await service.listCredentials(query.workspaceId);
448395 }, {
449396 query: t.Object({ workspaceId: t.String() }),
450397 })
451398
452399 .post("/api/credentials", async ({ body, set }) => {
453− if (!body.workspaceId || !body.sourceKey || !body.scope || !body.secretJson) {
454− set.status = 400;
455− return { error: "workspaceId, sourceKey, scope, and secretJson are required" };
456− }
457400 if (body.scope === "actor" && (!body.actorId || body.actorId.trim().length === 0)) {
458401 set.status = 400;
459402 return { error: "actorId is required for actor-scoped credential" };
@@ -493,10 +436,6 @@ const app = new Elysia() · 33 unmodified lines
493436 set.status = 401;
494437 return { error: "Unauthorized internal call" };
495438 }
496− if (!body.callId || !body.toolPath) {
497− set.status = 400;
498− return { error: "callId and toolPath are required" };
499− }
500439
501440 const call: ToolCallRequest = {
502441 runId: params.runId,