mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
init mcp data tests, added crud for $record
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, beforeAll } from "bun:test";
|
import { describe, test, expect, beforeAll } 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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,10 +16,10 @@ import { getSystemMcp } from "modules/mcp/system-mcp";
|
|||||||
* - [ ] data_entity_info
|
* - [ ] data_entity_info
|
||||||
* - [ ] config_data_get
|
* - [ ] config_data_get
|
||||||
* - [ ] config_data_update
|
* - [ ] config_data_update
|
||||||
* - [ ] config_data_entities_get
|
* - [x] config_data_entities_get
|
||||||
* - [ ] config_data_entities_add
|
* - [x] config_data_entities_add
|
||||||
* - [ ] config_data_entities_update
|
* - [x] config_data_entities_update
|
||||||
* - [ ] config_data_entities_remove
|
* - [x] config_data_entities_remove
|
||||||
* - [ ] config_data_relations_get
|
* - [ ] config_data_relations_get
|
||||||
* - [ ] config_data_relations_add
|
* - [ ] config_data_relations_add
|
||||||
* - [ ] config_data_relations_update
|
* - [ ] config_data_relations_update
|
||||||
@@ -44,5 +44,60 @@ describe("mcp data", 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_data_entities_{add,get,update,remove}", async () => {
|
||||||
|
const result = await tool(server, "config_data_entities_add", {
|
||||||
|
key: "test",
|
||||||
|
value: {},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.module).toBe("data");
|
||||||
|
expect(result.config.entities.test.type).toEqual("regular");
|
||||||
|
|
||||||
|
const entities = Object.keys(app.toJSON().data.entities ?? {});
|
||||||
|
expect(entities).toContain("test");
|
||||||
|
|
||||||
|
{
|
||||||
|
// get
|
||||||
|
const result = await tool(server, "config_data_entities_get", {
|
||||||
|
key: "test",
|
||||||
|
});
|
||||||
|
expect(result.module).toBe("data");
|
||||||
|
expect(result.key).toBe("test");
|
||||||
|
expect(result.value.type).toEqual("regular");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// update
|
||||||
|
const result = await tool(server, "config_data_entities_update", {
|
||||||
|
key: "test",
|
||||||
|
value: {
|
||||||
|
config: {
|
||||||
|
name: "Test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.module).toBe("data");
|
||||||
|
expect(result.config.entities.test.config?.name).toEqual("Test");
|
||||||
|
expect(app.toJSON().data.entities?.test?.config?.name).toEqual("Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// remove
|
||||||
|
const result = await tool(server, "config_data_entities_remove", {
|
||||||
|
key: "test",
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.module).toBe("data");
|
||||||
|
expect(app.toJSON().data.entities?.test).toBeUndefined();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
"hono": "4.8.3",
|
"hono": "4.8.3",
|
||||||
"json-schema-library": "10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"jsonv-ts": "^0.7.2",
|
"jsonv-ts": "^0.7.3",
|
||||||
"kysely": "0.27.6",
|
"kysely": "0.27.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function createMcpToolCaller() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ((res.result as any)?.isError) {
|
if ((res.result as any)?.isError) {
|
||||||
|
console.dir(res.result, { depth: null });
|
||||||
throw new Error((res.result as any)?.content?.[0]?.text ?? "Unknown error");
|
throw new Error((res.result as any)?.content?.[0]?.text ?? "Unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
|
|||||||
import { FieldClassMap } from "data/fields";
|
import { FieldClassMap } from "data/fields";
|
||||||
import { RelationClassMap, RelationFieldClassMap } from "data/relations";
|
import { RelationClassMap, RelationFieldClassMap } from "data/relations";
|
||||||
import { entityConfigSchema, entityTypes } from "data/entities";
|
import { entityConfigSchema, entityTypes } from "data/entities";
|
||||||
import { primaryFieldTypes } from "./fields";
|
import { primaryFieldTypes, baseFieldConfigSchema } from "./fields";
|
||||||
import { s } from "bknd/utils";
|
import { s } from "bknd/utils";
|
||||||
import { $object, $record } from "modules/mcp";
|
import { $object, $record } from "modules/mcp";
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ export const FIELDS = {
|
|||||||
...RelationFieldClassMap,
|
...RelationFieldClassMap,
|
||||||
media: { schema: mediaFieldConfigSchema, field: MediaField },
|
media: { schema: mediaFieldConfigSchema, field: MediaField },
|
||||||
};
|
};
|
||||||
|
export const FIELD_TYPES = Object.keys(FIELDS);
|
||||||
export type FieldType = keyof typeof FIELDS;
|
export type FieldType = keyof typeof FIELDS;
|
||||||
|
|
||||||
export const RELATIONS = RelationClassMap;
|
export const RELATIONS = RelationClassMap;
|
||||||
@@ -40,6 +41,19 @@ export const entitiesSchema = s.strictObject({
|
|||||||
fields: entityFields.optional(),
|
fields: entityFields.optional(),
|
||||||
});
|
});
|
||||||
export type TAppDataEntity = s.Static<typeof entitiesSchema>;
|
export type TAppDataEntity = s.Static<typeof entitiesSchema>;
|
||||||
|
export const simpleEntitiesSchema = s.strictObject({
|
||||||
|
type: s.string({ enum: entityTypes, default: "regular" }).optional(),
|
||||||
|
config: entityConfigSchema.optional(),
|
||||||
|
fields: s
|
||||||
|
.record(
|
||||||
|
s.object({
|
||||||
|
type: s.anyOf([s.string({ enum: FIELD_TYPES }), s.string()]),
|
||||||
|
config: baseFieldConfigSchema.optional(),
|
||||||
|
}),
|
||||||
|
{ default: {} },
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const relationsSchema = Object.entries(RelationClassMap).map(([name, relationClass]) => {
|
export const relationsSchema = Object.entries(RelationClassMap).map(([name, relationClass]) => {
|
||||||
return s.strictObject(
|
return s.strictObject(
|
||||||
@@ -70,6 +84,6 @@ export const dataConfigSchema = $object("config_data", {
|
|||||||
default: {},
|
default: {},
|
||||||
}).optional(),
|
}).optional(),
|
||||||
indices: $record("config_data_indices", indicesSchema, { default: {} }).optional(),
|
indices: $record("config_data_indices", indicesSchema, { default: {} }).optional(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type AppDataConfig = s.Static<typeof dataConfigSchema>;
|
export type AppDataConfig = s.Static<typeof dataConfigSchema>;
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export class RecordToolSchema<
|
|||||||
return this[mcpSchemaSymbol];
|
return this[mcpSchemaSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getNewSchema(fallback: s.Schema = this.additionalProperties) {
|
||||||
|
return this[opts].new_schema ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
private toolGet(node: s.Node<RecordToolSchema<AP, O>>) {
|
private toolGet(node: s.Node<RecordToolSchema<AP, O>>) {
|
||||||
return new Tool(
|
return new Tool(
|
||||||
[this.mcp.name, "get"].join("_"),
|
[this.mcp.name, "get"].join("_"),
|
||||||
@@ -60,8 +64,10 @@ export class RecordToolSchema<
|
|||||||
async (params, ctx: AppToolHandlerCtx) => {
|
async (params, ctx: AppToolHandlerCtx) => {
|
||||||
const configs = ctx.context.app.toJSON(params.secrets);
|
const configs = ctx.context.app.toJSON(params.secrets);
|
||||||
const config = getPath(configs, node.instancePath);
|
const config = getPath(configs, node.instancePath);
|
||||||
|
const [module_name] = node.instancePath;
|
||||||
|
|
||||||
// @todo: add schema to response
|
// @todo: add schema to response
|
||||||
|
const schema = params.schema ? this.getNewSchema().toJSON() : undefined;
|
||||||
|
|
||||||
if (params.key) {
|
if (params.key) {
|
||||||
if (!(params.key in config)) {
|
if (!(params.key in config)) {
|
||||||
@@ -70,15 +76,19 @@ export class RecordToolSchema<
|
|||||||
const value = getPath(config, params.key);
|
const value = getPath(config, params.key);
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
secrets: params.secrets ?? false,
|
secrets: params.secrets ?? false,
|
||||||
|
module: module_name,
|
||||||
key: params.key,
|
key: params.key,
|
||||||
value: value ?? null,
|
value: value ?? null,
|
||||||
|
schema,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
secrets: params.secrets ?? false,
|
secrets: params.secrets ?? false,
|
||||||
|
module: module_name,
|
||||||
key: null,
|
key: null,
|
||||||
value: config ?? null,
|
value: config ?? null,
|
||||||
|
schema,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -93,20 +103,26 @@ export class RecordToolSchema<
|
|||||||
key: s.string({
|
key: s.string({
|
||||||
description: "key to add",
|
description: "key to add",
|
||||||
}),
|
}),
|
||||||
value: this[opts].new_schema ?? this.additionalProperties,
|
value: this.getNewSchema(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
async (params, ctx: AppToolHandlerCtx) => {
|
async (params, ctx: AppToolHandlerCtx) => {
|
||||||
const configs = ctx.context.app.toJSON();
|
const configs = ctx.context.app.toJSON();
|
||||||
const config = getPath(configs, node.instancePath);
|
const config = getPath(configs, node.instancePath);
|
||||||
|
const [module_name, ...rest] = node.instancePath;
|
||||||
|
|
||||||
if (params.key in config) {
|
if (params.key in config) {
|
||||||
throw new Error(`Key "${params.key}" already exists in config`);
|
throw new Error(`Key "${params.key}" already exists in config`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ctx.context.app
|
||||||
|
.mutateConfig(module_name as any)
|
||||||
|
.patch([...rest, params.key], params.value);
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
key: params.key,
|
success: true,
|
||||||
value: params.value ?? null,
|
module: module_name,
|
||||||
|
config: ctx.context.app.module[module_name as any].config,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -121,22 +137,26 @@ export class RecordToolSchema<
|
|||||||
key: s.string({
|
key: s.string({
|
||||||
description: "key to update",
|
description: "key to update",
|
||||||
}),
|
}),
|
||||||
value: this[opts].new_schema ?? s.object({}),
|
value: this.getNewSchema(s.object({})),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
async (params, ctx: AppToolHandlerCtx) => {
|
async (params, ctx: AppToolHandlerCtx) => {
|
||||||
const configs = ctx.context.app.toJSON(params.secrets);
|
const configs = ctx.context.app.toJSON(params.secrets);
|
||||||
const config = getPath(configs, node.instancePath);
|
const config = getPath(configs, node.instancePath);
|
||||||
|
const [module_name, ...rest] = node.instancePath;
|
||||||
|
|
||||||
if (!(params.key in config)) {
|
if (!(params.key in config)) {
|
||||||
throw new Error(`Key "${params.key}" not found in config`);
|
throw new Error(`Key "${params.key}" not found in config`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = getPath(config, params.key);
|
await ctx.context.app
|
||||||
|
.mutateConfig(module_name as any)
|
||||||
|
.patch([...rest, params.key], params.value);
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
updated: false,
|
success: true,
|
||||||
key: params.key,
|
module: module_name,
|
||||||
value: value ?? null,
|
config: ctx.context.app.module[module_name as any].config,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -156,14 +176,20 @@ export class RecordToolSchema<
|
|||||||
async (params, ctx: AppToolHandlerCtx) => {
|
async (params, ctx: AppToolHandlerCtx) => {
|
||||||
const configs = ctx.context.app.toJSON();
|
const configs = ctx.context.app.toJSON();
|
||||||
const config = getPath(configs, node.instancePath);
|
const config = getPath(configs, node.instancePath);
|
||||||
|
const [module_name, ...rest] = node.instancePath;
|
||||||
|
|
||||||
if (!(params.key in config)) {
|
if (!(params.key in config)) {
|
||||||
throw new Error(`Key "${params.key}" not found in config`);
|
throw new Error(`Key "${params.key}" not found in config`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ctx.context.app
|
||||||
|
.mutateConfig(module_name as any)
|
||||||
|
.remove([...rest, params.key].join("."));
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
removed: false,
|
success: true,
|
||||||
key: params.key,
|
module: module_name,
|
||||||
|
config: ctx.context.app.module[module_name as any].config,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
4
bun.lock
4
bun.lock
@@ -35,7 +35,7 @@
|
|||||||
"hono": "4.8.3",
|
"hono": "4.8.3",
|
||||||
"json-schema-library": "10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"jsonv-ts": "^0.7.2",
|
"jsonv-ts": "^0.7.3",
|
||||||
"kysely": "0.27.6",
|
"kysely": "0.27.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
@@ -2511,7 +2511,7 @@
|
|||||||
|
|
||||||
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
||||||
|
|
||||||
"jsonv-ts": ["jsonv-ts@0.7.2", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-HxtHbMQhReJpxDIWHcM+kLekRLgJIo+drQnxiXep9thbh5jA44pd3DxwApEV1/oTufH2xAfDV6uu6O0Fd4s9lA=="],
|
"jsonv-ts": ["jsonv-ts@0.7.3", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-1P/ouF/a84Rc7NCXfSGPmkttyBFqemHE+5tZjb7hyaTs8MxmVUkuUO+d80/uu8sguzTnd3MmAuyuLAM0HQT4cA=="],
|
||||||
|
|
||||||
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user