Skip to content

Commit 9d3cc4c

Browse files
feat: init plugin framework + object storage plugin (#2198)
* plugins as simple sidebar items + route trees * wip * wip * wip * refactor(plugins): extract store plugin and create mesh-sdk package - Move store components from mesh app to mesh-plugin-store package - Create @decocms/mesh-sdk package with shared hooks (useConnection, useToolCall, useMcp, etc.) - Add PluginContext system with typed tool caller support - Add PluginLayout for consistent plugin rendering with connection selection - Support partial context for empty states via usePluginContext({ partial: true }) - Move ReadmeViewer and GitHub utilities to @deco/ui - Clean up unused store routes and components from mesh app * same react version for everyone * fix pkg import * rename constant * fix css gen * refactor: revert store to core app + create object storage plugin Part 1: Revert Store Changes - Move store components back from mesh-plugin-store to apps/mesh/ - Restore store routes in mesh app router - Delete packages/mesh-plugin-store package - Keep plugin infrastructure (PluginContext, PluginLayout, mesh-sdk) Part 2: Create Object Storage Plugin - Add OBJECT_STORAGE_BINDING in packages/bindings with tools: - LIST_OBJECTS, GET_OBJECT_METADATA, GET_PRESIGNED_URL - PUT_PRESIGNED_URL, DELETE_OBJECT, DELETE_OBJECTS - Create packages/mesh-plugin-object-storage with: - File browser UI with list view - Breadcrumb navigation - Upload/download via presigned URLs - Single and batch delete - Infinite scroll pagination - Register plugin in apps/mesh/src/web/plugins.ts * fix: restore store sidebar item and missing files from main - Add Store back to sidebar navigation - Restore use-gateway-system-prompt.ts, use-system.ts, theme-provider.tsx - Restore public-config.ts API route - Restore ThemeConfig in core/config.ts - Add gatewaySystemPrompts to LOCALSTORAGE_KEYS - Add publicConfig to KEYS * some reverts * fix * rn * wip * plugin is a flag for the org * fix * fixes * now the ui is good * fixes * fix intersection objserver usage * rename my migration * remove unused stuff * update defaults * minor ui fixes
1 parent 1982b57 commit 9d3cc4c

62 files changed

Lines changed: 4988 additions & 104 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
"@astrojs/mdx": "^4.3.1",
2121
"@astrojs/react": "^4.3.0",
2222
"@tailwindcss/vite": "^4.0.7",
23-
"@types/react": "^19.1.8",
24-
"@types/react-dom": "^19.1.6",
2523
"astro": "^5.6.1",
2624
"lucide-react": "^0.525.0",
2725
"react": "^19.2.0",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Kysely } from "kysely";
2+
3+
export async function up(db: Kysely<unknown>): Promise<void> {
4+
await db.schema
5+
.alterTable("organization_settings")
6+
.addColumn("enabled_plugins", "text")
7+
.execute();
8+
}
9+
10+
export async function down(db: Kysely<unknown>): Promise<void> {
11+
await db.schema
12+
.alterTable("organization_settings")
13+
.dropColumn("enabled_plugins")
14+
.execute();
15+
}

apps/mesh/migrations/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as migration016downstreamtokenclientinfo from "./016-downstream-token-c
1818
import * as migration017downstreamtokenremoveuserid from "./017-downstream-token-remove-userid.ts";
1919
import * as migration018dropgatewaytoolselectionstrategy from "./018-drop-gateway-tool-selection-strategy.ts";
2020
import * as migration019removegatewayisdefault from "./019-remove-gateway-is-default.ts";
21+
import * as migration020enabledplugins from "./020-enabled-plugins.ts";
2122

2223
const migrations = {
2324
"001-initial-schema": migration001initialschema,
@@ -40,6 +41,7 @@ const migrations = {
4041
"018-drop-gateway-tool-selection-strategy":
4142
migration018dropgatewaytoolselectionstrategy,
4243
"019-remove-gateway-is-default": migration019removegatewayisdefault,
44+
"020-enabled-plugins": migration020enabledplugins,
4345
} satisfies Record<string, Migration>;
4446

4547
export default migrations;

apps/mesh/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@deco/ui": "workspace:*",
5151
"@decocms/better-auth": "1.5.17",
5252
"@decocms/bindings": "workspace:*",
53+
"@decocms/mesh-sdk": "workspace:*",
5354
"@decocms/runtime": "workspace:*",
5455
"@decocms/vite-plugin": "workspace:*",
5556
"@hookform/resolvers": "^5.2.2",
@@ -84,8 +85,6 @@
8485
"@tiptap/suggestion": "^3.13.0",
8586
"@types/bun": "^1.3.1",
8687
"@types/pg": "^8.15.6",
87-
"@types/react": "^19.2.2",
88-
"@types/react-dom": "^19.2.2",
8988
"@vercel/nft": "^1.1.1",
9089
"@vitejs/plugin-react": "^5.1.0",
9190
"ai": "^6.0.1",
@@ -117,7 +116,8 @@
117116
"vite-tsconfig-paths": "^5.1.4",
118117
"zod": "^4.0.0",
119118
"zod-from-json-schema": "^0.5.2",
120-
"zustand": "^5.0.9"
119+
"zustand": "^5.0.9",
120+
"mesh-plugin-object-storage": "workspace:*"
121121
},
122122
"module": "src/index.ts",
123123
"keywords": [

apps/mesh/src/storage/organization-settings.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,43 @@ export class OrganizationSettingsStorage
2525
? JSON.parse(record.sidebar_items)
2626
: record.sidebar_items
2727
: null,
28+
enabled_plugins: record.enabled_plugins
29+
? typeof record.enabled_plugins === "string"
30+
? JSON.parse(record.enabled_plugins)
31+
: record.enabled_plugins
32+
: null,
2833
createdAt: record.createdAt,
2934
updatedAt: record.updatedAt,
3035
};
3136
}
3237

3338
async upsert(
3439
organizationId: string,
35-
data?: Partial<Pick<OrganizationSettings, "sidebar_items">>,
40+
data?: Partial<
41+
Pick<OrganizationSettings, "sidebar_items" | "enabled_plugins">
42+
>,
3643
): Promise<OrganizationSettings> {
3744
const now = new Date().toISOString();
3845
const sidebarItemsJson = data?.sidebar_items
3946
? JSON.stringify(data.sidebar_items)
4047
: null;
48+
const enabledPluginsJson = data?.enabled_plugins
49+
? JSON.stringify(data.enabled_plugins)
50+
: null;
4151

4252
await this.db
4353
.insertInto("organization_settings")
4454
.values({
4555
organizationId,
4656
sidebar_items: sidebarItemsJson,
57+
enabled_plugins: enabledPluginsJson,
4758
createdAt: now,
4859
updatedAt: now,
4960
})
5061
.onConflict((oc) =>
5162
oc.column("organizationId").doUpdateSet({
5263
sidebar_items: sidebarItemsJson ? sidebarItemsJson : undefined,
64+
enabled_plugins: enabledPluginsJson ? enabledPluginsJson : undefined,
5365
updatedAt: now,
5466
}),
5567
)
@@ -61,6 +73,7 @@ export class OrganizationSettingsStorage
6173
return {
6274
organizationId,
6375
sidebar_items: data?.sidebar_items ?? null,
76+
enabled_plugins: data?.enabled_plugins ?? null,
6477
createdAt: now,
6578
updatedAt: now,
6679
};

apps/mesh/src/storage/ports.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ export interface OrganizationSettingsStoragePort {
4040
get(organizationId: string): Promise<OrganizationSettings | null>;
4141
upsert(
4242
organizationId: string,
43-
data?: Partial<Pick<OrganizationSettings, "sidebar_items">>,
43+
data?: Partial<
44+
Pick<OrganizationSettings, "sidebar_items" | "enabled_plugins">
45+
>,
4446
): Promise<OrganizationSettings>;
4547
}
4648

apps/mesh/src/storage/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,15 @@ export interface SidebarItem {
127127
export interface OrganizationSettingsTable {
128128
organizationId: string;
129129
sidebar_items: JsonArray<SidebarItem[]> | null;
130+
enabled_plugins: JsonArray<string[]> | null;
130131
createdAt: ColumnType<Date, Date | string, never>;
131132
updatedAt: ColumnType<Date, Date | string, Date | string>;
132133
}
133134

134135
export interface OrganizationSettings {
135136
organizationId: string;
136137
sidebar_items: SidebarItem[] | null;
138+
enabled_plugins: string[] | null;
137139
createdAt: Date | string;
138140
updatedAt: Date | string;
139141
}

apps/mesh/src/tools/client.ts

Lines changed: 13 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1+
/**
2+
* Tool caller utilities re-exported from mesh-sdk for backwards compatibility.
3+
* New code should import from @decocms/mesh-sdk directly.
4+
*/
5+
16
import type { ToolBinder } from "@/core/define-tool";
27
import type z from "zod";
38
import type { MCPMeshTools } from "./index.ts";
49

10+
// Re-export from mesh-sdk
11+
export {
12+
createToolCaller,
13+
UNKNOWN_CONNECTION_ID,
14+
type ToolCaller,
15+
} from "@decocms/mesh-sdk";
16+
17+
// Mesh-specific types that stay here
518
export type MCPClient<
619
TDefinition extends readonly ToolBinder<z.ZodTypeAny, z.ZodTypeAny>[],
720
> = {
@@ -14,84 +27,3 @@ export type MCPClient<
1427
};
1528

1629
export type MeshClient = MCPClient<MCPMeshTools>;
17-
18-
export const UNKNOWN_CONNECTION_ID = "UNKNOWN_CONNECTION_ID";
19-
20-
const parseSSEResponseAsJson = async (response: Response) => {
21-
/**
22-
* example:
23-
* 'event: message\ndata: {"result":{"content":[{"type":"text","text":"{\\"organizations\\":[{\\"id\\":\\"1\\",\\"name\\":\\"Organization 1\\",\\"slug\\":\\"organization-1\\",\\"createdAt\\":\\"2025-11-03T18:12:46.700Z\\"}]}"}],"structuredContent":{"organizations":[{"id":"1","name":"Organization 1","slug":"organization-1","createdAt":"2025-11-03T18:12:46.700Z"}]}},"jsonrpc":"2.0","id":1}\n\n'
24-
*/
25-
const raw = await response.text();
26-
const data = raw.split("\n").find((line) => line.startsWith("data: "));
27-
28-
if (!data) {
29-
throw new Error("No data received from the server");
30-
}
31-
32-
const json = JSON.parse(data.replace("data: ", ""));
33-
34-
return json;
35-
};
36-
37-
/**
38-
* Type for a generic tool caller function
39-
*/
40-
export type ToolCaller<TArgs = unknown, TOutput = unknown> = (
41-
toolName: string,
42-
args: TArgs,
43-
) => Promise<TOutput>;
44-
45-
/**
46-
* Create a unified tool caller
47-
*
48-
* - If connectionId is provided: routes to /mcp/:connectionId (connection-specific tools)
49-
* - If connectionId is omitted/null: routes to /mcp (mesh API tools)
50-
*
51-
* This abstracts the routing logic so hooks don't need to know if they're
52-
* calling mesh tools or connection-specific tools.
53-
*/
54-
export function createToolCaller<TArgs = unknown, TOutput = unknown>(
55-
connectionId?: string,
56-
): ToolCaller<TArgs, TOutput> {
57-
if (connectionId === UNKNOWN_CONNECTION_ID) {
58-
return (async () => {}) as unknown as ToolCaller<TArgs, TOutput>;
59-
}
60-
61-
const endpoint = connectionId ? `/mcp/${connectionId}` : "/mcp";
62-
63-
return async (toolName: string, args: TArgs): Promise<TOutput> => {
64-
const response = await fetch(endpoint, {
65-
method: "POST",
66-
body: JSON.stringify({
67-
jsonrpc: "2.0",
68-
id: 1,
69-
method: "tools/call",
70-
params: {
71-
name: toolName,
72-
arguments: args,
73-
},
74-
}),
75-
headers: {
76-
"Content-Type": "application/json",
77-
Accept: "application/json, text/event-stream",
78-
},
79-
credentials: "include",
80-
});
81-
82-
if (!response.ok) {
83-
throw new Error(`HTTP error! status: ${response.status}`);
84-
}
85-
86-
const json =
87-
response.headers.get("Content-Type") === "application/json"
88-
? await response.json()
89-
: await parseSSEResponseAsJson(response);
90-
91-
if (json.result?.isError) {
92-
throw new Error(json.result.content?.[0]?.text || "Tool call failed");
93-
}
94-
95-
return json.result?.structuredContent || json.result;
96-
};
97-
}

apps/mesh/src/tools/organization/settings-get.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const ORGANIZATION_SETTINGS_GET = defineTool({
1212
outputSchema: z.object({
1313
organizationId: z.string(),
1414
sidebar_items: z.array(SidebarItemSchema).nullable().optional(),
15+
enabled_plugins: z.array(z.string()).nullable().optional(),
1516
createdAt: z.string().datetime().optional().describe("ISO 8601 timestamp"),
1617
updatedAt: z.string().datetime().optional().describe("ISO 8601 timestamp"),
1718
}),

apps/mesh/src/tools/organization/settings-update.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ export const ORGANIZATION_SETTINGS_UPDATE = defineTool({
1010
inputSchema: z.object({
1111
organizationId: z.string(),
1212
sidebar_items: z.array(SidebarItemSchema).optional(),
13+
enabled_plugins: z.array(z.string()).optional(),
1314
}),
1415

1516
outputSchema: z.object({
1617
organizationId: z.string(),
1718
sidebar_items: z.array(SidebarItemSchema).nullable().optional(),
19+
enabled_plugins: z.array(z.string()).nullable().optional(),
1820
createdAt: z.string().datetime().describe("ISO 8601 timestamp"),
1921
updatedAt: z.string().datetime().describe("ISO 8601 timestamp"),
2022
}),
@@ -31,6 +33,7 @@ export const ORGANIZATION_SETTINGS_UPDATE = defineTool({
3133
input.organizationId,
3234
{
3335
sidebar_items: input.sidebar_items,
36+
enabled_plugins: input.enabled_plugins,
3437
},
3538
);
3639

0 commit comments

Comments
 (0)