added mcp tests for media

This commit is contained in:
dswbx
2025-08-12 20:57:13 +02:00
parent bd3d2ea900
commit a6ed74d904
5 changed files with 112 additions and 14 deletions

View File

@@ -1,19 +1,23 @@
import { describe, it, expect, beforeAll } from "bun:test"; import { describe, test, expect, beforeAll, beforeEach, afterAll } from "bun:test";
import { type App, createApp } from "core/test/utils"; import { type App, createApp, createMcpToolCaller } from "core/test/utils";
import { getSystemMcp } from "modules/mcp/system-mcp"; import { getSystemMcp } from "modules/mcp/system-mcp";
import { registries } from "index"; import { registries } from "index";
import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
import { disableConsoleLog, enableConsoleLog } from "core/utils";
beforeAll(disableConsoleLog);
afterAll(enableConsoleLog);
/** /**
* - [ ] config_media_get * - [x] config_media_get
* - [ ] config_media_update * - [x] config_media_update
* - [ ] config_media_adapter_get * - [x] config_media_adapter_get
* - [ ] config_media_adapter_update * - [x] config_media_adapter_update
*/ */
describe("mcp media", async () => { describe("mcp media", async () => {
let app: App; let app: App;
let server: ReturnType<typeof getSystemMcp>; let server: ReturnType<typeof getSystemMcp>;
beforeAll(async () => { beforeEach(async () => {
registries.media.register("local", StorageLocalAdapter); registries.media.register("local", StorageLocalAdapter);
app = createApp({ app = createApp({
initialConfig: { initialConfig: {
@@ -35,5 +39,79 @@ describe("mcp media", async () => {
}); });
await app.build(); await app.build();
server = getSystemMcp(app); server = getSystemMcp(app);
server.setLogLevel("error");
server.onNotification((message) => {
console.dir(message, { depth: null });
});
});
const tool = createMcpToolCaller();
test("config_media_{get,update}", async () => {
const result = await tool(server, "config_media_get", {});
expect(result).toEqual({
path: "",
secrets: false,
partial: false,
value: app.toJSON().media,
});
// partial
expect((await tool(server, "config_media_get", { path: "adapter" })).value).toEqual({
type: "local",
config: {
path: "./",
},
});
// update
await tool(server, "config_media_update", {
value: {
storage: {
body_max_size: 1024 * 1024 * 10,
},
},
return_config: true,
});
expect(app.toJSON().media.storage.body_max_size).toBe(1024 * 1024 * 10);
});
test("config_media_adapter_{get,update}", async () => {
const result = await tool(server, "config_media_adapter_get", {});
expect(result).toEqual({
secrets: false,
value: app.toJSON().media.adapter,
});
// update
await tool(server, "config_media_adapter_update", {
value: {
type: "local",
config: {
path: "./subdir",
},
},
});
const adapter = app.toJSON().media.adapter as any;
expect(adapter.config.path).toBe("./subdir");
expect(adapter.type).toBe("local");
// set to s3
{
await tool(server, "config_media_adapter_update", {
value: {
type: "s3",
config: {
access_key: "123",
secret_access_key: "456",
url: "https://example.com/what",
},
},
});
const adapter = app.toJSON(true).media.adapter as any;
expect(adapter.type).toBe("s3");
expect(adapter.config.url).toBe("https://example.com/what");
}
}); });
}); });

View File

@@ -177,7 +177,6 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
this.throwIfRestricted(partial); this.throwIfRestricted(partial);
// overwrite arrays and primitives, only deep merge objects
// @ts-ignore // @ts-ignore
const config = set(current, path, value); const config = set(current, path, value);

View File

@@ -39,6 +39,7 @@ export function buildMediaSchema() {
}, },
{ default: {} }, { default: {} },
), ),
// @todo: currently cannot be updated partially using mcp
adapter: $schema( adapter: $schema(
"config_media_adapter", "config_media_adapter",
s.anyOf(Object.values(adapterSchemaObject)), s.anyOf(Object.values(adapterSchemaObject)),

View File

@@ -50,12 +50,28 @@ export const $schema = <
{ {
...mcp.getToolOptions("update"), ...mcp.getToolOptions("update"),
inputSchema: s.strictObject({ inputSchema: s.strictObject({
full: s.boolean({ default: false }).optional(),
value: schema as any, value: schema as any,
return_config: s.boolean({ default: false }).optional(),
secrets: s.boolean({ default: false }).optional(),
}), }),
}, },
async (params, ctx: AppToolHandlerCtx) => { async (params, ctx: AppToolHandlerCtx) => {
return ctx.json(params); const { value, return_config, secrets } = params;
const [module_name, ...rest] = node.instancePath;
await ctx.context.app.mutateConfig(module_name as any).overwrite(rest, value);
let config: any = undefined;
if (return_config) {
const configs = ctx.context.app.toJSON(secrets);
config = getPath(configs, node.instancePath);
}
return ctx.json({
success: true,
module: module_name,
config,
});
}, },
); );
}; };

View File

@@ -2910,10 +2910,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"full": {
"type": "boolean",
"default": false
},
"value": { "value": {
"anyOf": [ "anyOf": [
{ {
@@ -3028,6 +3024,14 @@
] ]
} }
] ]
},
"return_config": {
"type": "boolean",
"default": false
},
"secrets": {
"type": "boolean",
"default": false
} }
} }
}, },