Skip to content

Commit db03710

Browse files
committed
Send toolkits' storage row ids when forwarding to v2
1 parent 3e7fb0c commit db03710

2 files changed

Lines changed: 37 additions & 29 deletions

File tree

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
// ---------------------------------------------------------------------------
2-
// Resolving a toolkit URL's slug to the toolkit's row id, for forwarding.
2+
// Resolving a toolkit URL's slug to the toolkit's storage row id, for forwarding.
33
//
44
// Toolkits live in plugin storage (`@executor-js/plugin-toolkits`), owned by the
5-
// org or by a single user. This reads them the way the v1 session does: through
6-
// an executor scoped to the caller and org, so the caller sees the org's
7-
// toolkits plus their own personal ones and nobody else's. The executor is
8-
// built with the toolkits plugin alone, because a forwarded request has no use
9-
// for the rest of the catalog. Among the rows with the slug the org's toolkit
10-
// wins over the caller's own, as in the plugin's own slug resolution
11-
// (`getBySlugEntry`).
5+
// org or by a single user. v2 imported each toolkit under its `plugin_storage`
6+
// row id (not the toolkit's own `id`, which is the row's `key`), and derives the
7+
// toolkit's v2 connection from that row id, so this reads the row id itself.
8+
// The plugin's API hides it, hence the direct read. Visibility matches the v1
9+
// session: the org's toolkits plus the caller's own personal ones, nobody
10+
// else's. Among rows with the slug the org's toolkit wins over the caller's own,
11+
// as in the plugin's slug resolution (`getBySlugEntry`).
1212
// ---------------------------------------------------------------------------
1313

14-
import { Effect, Layer } from "effect";
14+
import { sql } from "drizzle-orm";
15+
import { Effect } from "effect";
1516

16-
import { PluginsProvider, makeScopedExecutor } from "@executor-js/api/server";
17-
import { toolkitsPlugin } from "@executor-js/plugin-toolkits/server";
18-
19-
import { CloudDbProvider, CloudHostConfig } from "../engine/execution-stack";
20-
import { makeDbLayer } from "../db/db";
21-
22-
const toolkitsOnly = Layer.succeed(PluginsProvider)({ plugins: () => [toolkitsPlugin()] });
17+
import { DbService, makeDbLayer } from "../db/db";
2318

2419
export const resolveToolkitRowId = (
2520
organizationId: string,
@@ -28,17 +23,17 @@ export const resolveToolkitRowId = (
2823
): Promise<string | null> =>
2924
Effect.runPromise(
3025
Effect.gen(function* () {
31-
const executor = yield* makeScopedExecutor<readonly [ReturnType<typeof toolkitsPlugin>]>(
32-
accountId,
33-
organizationId,
34-
"",
26+
const { db } = yield* DbService;
27+
const result = yield* Effect.promise(() =>
28+
db.execute<{ row_id: string }>(sql`
29+
select row_id from plugin_storage
30+
where tenant = ${organizationId} and plugin_id = 'toolkits' and collection = 'toolkits'
31+
and (owner = 'org' or (owner = 'user' and subject = ${accountId}))
32+
and (data::jsonb)->>'slug' = ${slug}
33+
order by case when owner = 'org' then 0 else 1 end, key
34+
limit 1
35+
`),
3536
);
36-
const toolkits = yield* executor.toolkits.list();
37-
const matches = toolkits.filter((toolkit) => toolkit.slug === slug);
38-
return (matches.find((toolkit) => toolkit.owner === "org") ?? matches[0])?.id ?? null;
39-
}).pipe(
40-
Effect.provide(Layer.mergeAll(CloudDbProvider, toolkitsOnly, CloudHostConfig)),
41-
Effect.provide(makeDbLayer()),
42-
Effect.scoped,
43-
),
37+
return [...result][0]?.row_id ?? null;
38+
}).pipe(Effect.provide(makeDbLayer()), Effect.scoped),
4439
);

‎apps/cloud/src/mcp/v2-moved-flag.node.test.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// ---------------------------------------------------------------------------
1313

1414
import { describe, expect, it } from "@effect/vitest";
15+
import { sql } from "drizzle-orm";
1516
import { Effect, Layer } from "effect";
1617

1718
import { UserStoreService, makeUserStoreLayer } from "../auth/context";
@@ -107,7 +108,19 @@ describe("toolkit slug lookup", () => {
107108
"",
108109
);
109110
const created = yield* executor.toolkits.create({ owner, name: slug, slug });
110-
return created.id;
111+
// v2 keys an imported toolkit by its storage row id, which is not the toolkit's id.
112+
const { db } = yield* DbService;
113+
const rows = yield* Effect.promise(() =>
114+
db.execute<{ row_id: string }>(sql`
115+
select row_id from plugin_storage
116+
where tenant = ${organizationId} and plugin_id = 'toolkits'
117+
and collection = 'toolkits' and key = ${created.id}
118+
`),
119+
);
120+
const rowId = [...rows][0]?.row_id;
121+
expect(rowId).toBeDefined();
122+
expect(rowId).not.toBe(created.id);
123+
return rowId;
111124
}).pipe(
112125
Effect.provide(Layer.mergeAll(CloudDbProvider, toolkitsOnly, CloudHostConfig)),
113126
Effect.provide(makeDbLayer()),

0 commit comments

Comments
 (0)