feat(mcp): contracts, capability stats migration and MCP SDK dependency
- contracts/mcp.ts: capability stats/tools, MCP server status/config schemas, mcpToolVisible - api.ts: GET /capabilities/stats, GET /capabilities/tools, GET /mcp/status, PATCH /mcp/config - migration 0039: mcp_server_config single row + audit_event capability index - deno.json: npm:@modelcontextprotocol/[email protected].1 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
efb5d206c1
commit
73ee3c6614
8 files changed
+294
-10
No files matched your search
@@ -22,6 +22,8 @@
|
||||
"@hono/swagger-ui": "npm:@hono/[email protected]",
|
||||
"@simplewebauthn/server": "npm:@simplewebauthn/[email protected]",
|
||||
"@hono/zod-openapi": "npm:@hono/[email protected]",
|
||||
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/[email protected]",
|
||||
"@modelcontextprotocol/sdk/": "npm:/@modelcontextprotocol/[email protected]/",
|
||||
"@types/node": "npm:@types/[email protected]",
|
||||
"@types/nunjucks": "npm:@types/[email protected]",
|
||||
"@types/pg": "npm:@types/[email protected]",
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
"npm:@babel/[email protected]": "7.29.9",
|
||||
"npm:@biomejs/biome@*": "2.5.14",
|
||||
"npm:@biomejs/[email protected]": "2.5.14",
|
||||
"npm:@earendil-works/[email protected]": "[email protected]",
|
||||
"npm:@earendil-works/[email protected]": "[email protected]",
|
||||
"npm:@earendil-works/[email protected]": "0.87.1_@[email protected][email protected]_[email protected]",
|
||||
"npm:@earendil-works/[email protected]": "0.87.1_@[email protected][email protected]_[email protected]",
|
||||
"npm:@fontsource-variable/inter-tight@^5.2.7": "5.3.0",
|
||||
"npm:@fontsource-variable/space-grotesk@^5.2.10": "5.3.0",
|
||||
"npm:@hono/[email protected]": "[email protected]",
|
||||
"npm:@hono/[email protected]": "[email protected][email protected]",
|
||||
"npm:@modelcontextprotocol/[email protected]": "[email protected]",
|
||||
"npm:@react-router/dev@*": "[email protected][email protected][email protected]_@[email protected][email protected][email protected][email protected]",
|
||||
"npm:@react-router/[email protected]": "[email protected][email protected][email protected]_@[email protected][email protected][email protected][email protected]",
|
||||
"npm:@react-router/[email protected]": "[email protected][email protected][email protected][email protected]",
|
||||
@@ -635,7 +636,7 @@
|
||||
"esbuild"
|
||||
]
|
||||
},
|
||||
"@earendil-works/[email protected][email protected]": {
|
||||
"@earendil-works/[email protected]_@[email protected][email protected]_[email protected]": {
|
||||
"integrity": "sha512-Zev3B0HK7YS5A4EZQ2XnEqiJuirx6QBiltJ+LpmjV5a/+2IU0cfKtIfnkNkORK707XOvKBY2WRtk7cAwHpbh2Q==",
|
||||
"dependencies": [
|
||||
"@earendil-works/chord",
|
||||
@@ -647,7 +648,7 @@
|
||||
"[email protected]"
|
||||
]
|
||||
},
|
||||
"@earendil-works/[email protected][email protected]": {
|
||||
"@earendil-works/[email protected]_@[email protected][email protected]_[email protected]": {
|
||||
"integrity": "sha512-X/3PfQBnnoeVdO9Cv8zHghUMglzlgNZYGNzoPnbRoGnHl3Rw3TlA2UKSUB7BRHUOxMryHXYa8dnjWZlbRheDZA==",
|
||||
"dependencies": [
|
||||
"@anthropic-ai/sdk",
|
||||
@@ -826,14 +827,18 @@
|
||||
"@fontsource-variable/[email protected]": {
|
||||
"integrity": "sha512-2IxmvfB08i9vnGB3Ym/AXvhRE+8XOjWMXIyDum03c+tPwH0FUoMNQfGpU8NXPxjbws0Vvss3AH0Zqt4oJBBAdw=="
|
||||
},
|
||||
"@google/[email protected]": {
|
||||
"@google/[email protected]_@[email protected][email protected][email protected]": {
|
||||
"integrity": "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==",
|
||||
"dependencies": [
|
||||
"@modelcontextprotocol/[email protected][email protected]",
|
||||
"google-auth-library",
|
||||
"p-retry",
|
||||
"protobufjs",
|
||||
"ws"
|
||||
],
|
||||
"optionalPeers": [
|
||||
"@modelcontextprotocol/[email protected][email protected]"
|
||||
],
|
||||
"scripts": true
|
||||
},
|
||||
"@hexagon/[email protected]": {
|
||||
@@ -961,8 +966,29 @@
|
||||
"[email protected]",
|
||||
"pkce-challenge",
|
||||
"raw-body",
|
||||
"[email protected]",
|
||||
"zod-to-json-schema"
|
||||
"zod[email protected]_zod@3.25.76"
|
||||
]
|
||||
},
|
||||
"@modelcontextprotocol/[email protected][email protected]": {
|
||||
"integrity": "sha512-H2HxLvC3HDNybePJaLdSrU1hhUK5iQw+WvV1b01myFyI7sdVGe1u/IPTE5D9fGCiJDVtgMV/lmFkQXLmQyIFYA==",
|
||||
"dependencies": [
|
||||
"@hono/node-server",
|
||||
"ajv",
|
||||
"[email protected][email protected]",
|
||||
"[email protected]",
|
||||
"cors",
|
||||
"cross-spawn",
|
||||
"eventsource",
|
||||
"eventsource-parser",
|
||||
"express",
|
||||
"express-rate-limit",
|
||||
"hono",
|
||||
"jose",
|
||||
"[email protected]",
|
||||
"pkce-challenge",
|
||||
"raw-body",
|
||||
"[email protected]",
|
||||
"[email protected][email protected]"
|
||||
]
|
||||
},
|
||||
"@mswjs/[email protected]": {
|
||||
@@ -5180,7 +5206,7 @@
|
||||
"@babel/plugin-transform-typescript",
|
||||
"@babel/preset-typescript",
|
||||
"@dotenvx/dotenvx",
|
||||
"@modelcontextprotocol/sdk",
|
||||
"@modelcontextprotocol/sdk@[email protected]",
|
||||
"@types/validate-npm-package-name",
|
||||
"browserslist",
|
||||
"[email protected]",
|
||||
@@ -5208,7 +5234,7 @@
|
||||
"tsconfig-paths",
|
||||
"validate-npm-package-name",
|
||||
"[email protected]",
|
||||
"zod-to-json-schema"
|
||||
"zod-to-json-schema@[email protected]"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
@@ -5777,9 +5803,12 @@
|
||||
"integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="
|
||||
},
|
||||
"[email protected][email protected]": {
|
||||
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="
|
||||
},
|
||||
"[email protected][email protected]": {
|
||||
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
|
||||
"dependencies": [
|
||||
"zod@3.25.76"
|
||||
"zod@4.6.5"
|
||||
]
|
||||
},
|
||||
"[email protected]": {
|
||||
@@ -5796,6 +5825,7 @@
|
||||
"npm:@earendil-works/[email protected]",
|
||||
"npm:@hono/[email protected]",
|
||||
"npm:@hono/[email protected]",
|
||||
"npm:@modelcontextprotocol/[email protected]",
|
||||
"npm:@simplewebauthn/[email protected]",
|
||||
"npm:@types/[email protected]",
|
||||
"npm:@types/[email protected]",
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as execution from "./execution.ts";
|
||||
import * as files from "./files.ts";
|
||||
import * as identity from "./identity.ts";
|
||||
import * as instances from "./instances.ts";
|
||||
import * as mcp from "./mcp.ts";
|
||||
import * as ops from "./ops.ts";
|
||||
import * as organization from "./organization.ts";
|
||||
import * as pi from "./pi.ts";
|
||||
@@ -1564,6 +1565,40 @@ export const Api = {
|
||||
body: z.object({ input: z.unknown(), preview: z.boolean().default(false) }),
|
||||
response: z.object({ output: z.unknown(), taskId: Id.nullable(), planId: Id.nullable() }),
|
||||
}),
|
||||
stats: ep({
|
||||
method: "get",
|
||||
path: "/capabilities/stats",
|
||||
scope: "read",
|
||||
summary: "能力调用统计:每能力调用数、失败数、平均耗时、最近调用、调用来源(内置 Agent / MCP / REST)",
|
||||
query: mcp.CapabilityStatsQuery,
|
||||
response: mcp.CapabilityStatsResponse,
|
||||
}),
|
||||
tools: ep({
|
||||
method: "get",
|
||||
path: "/capabilities/tools",
|
||||
scope: "read",
|
||||
summary: "能力清单:描述符、工具名、输入输出 JSON Schema、当前授权状态",
|
||||
response: mcp.CapabilityToolsResponse,
|
||||
}),
|
||||
},
|
||||
|
||||
// ── 对外 MCP 服务端(Streamable HTTP 在 /mcp,不进 OpenAPI;这里是状态与配置)──
|
||||
mcp: {
|
||||
status: ep({
|
||||
method: "get",
|
||||
path: "/mcp/status",
|
||||
scope: "admin",
|
||||
summary: "MCP 服务端状态:启用、允许的 API Key、当前会话、最近调用、限速",
|
||||
response: mcp.McpServerStatus,
|
||||
}),
|
||||
update: ep({
|
||||
method: "patch",
|
||||
path: "/mcp/config",
|
||||
scope: "admin",
|
||||
summary: "修改 MCP 服务端配置(启用开关、允许的 API Key)",
|
||||
body: mcp.McpServerConfigUpdate,
|
||||
response: mcp.McpServerConfig,
|
||||
}),
|
||||
},
|
||||
|
||||
// ── 设置 ──
|
||||
@@ -2328,3 +2363,4 @@ export type EndpointResponse<E extends EndpointDef> = z.infer<E["response"]>;
|
||||
|
||||
export * from "./files.ts";
|
||||
export * from "./schedules.ts";
|
||||
export * from "./mcp.ts";
|
||||
@@ -0,0 +1,162 @@
|
||||
import { z } from "zod";
|
||||
import { CapabilityDescriptor, type CapabilityKind } from "./capability.ts";
|
||||
import { type ApiScope, Id, Timestamp } from "./common.ts";
|
||||
|
||||
// 对外 MCP 服务端与能力可视化(REQ R13、§10.3、§12.3)。
|
||||
// MCP 每个 tool 对应一个已注册能力(工具名 `域__动作`),调用走同一个 registry.invoke(授权 / 审计 / 幂等键 / 操作账本)。
|
||||
|
||||
/** MCP 端点(相对 API_PREFIX);Streamable HTTP,POST 发消息、GET 开服务端推送流、DELETE 结束会话 */
|
||||
export const MCP_PATH = "/mcp";
|
||||
/** MCP 协议里声明的服务端名 */
|
||||
export const MCP_SERVER_NAME = "uhd-media-tool";
|
||||
|
||||
/** 调用来源归类:内置 Agent(含后台任务)、MCP、REST(控制台 / API Key 直接调用) */
|
||||
export const CapabilityCaller = z.enum(["agent", "mcp", "rest", "other"]);
|
||||
export type CapabilityCaller = z.infer<typeof CapabilityCaller>;
|
||||
|
||||
/** 审计 payload.source → 调用来源 */
|
||||
export function callerOfSource(source: string | null | undefined): CapabilityCaller {
|
||||
if (source === "agent" || source === "task") return "agent";
|
||||
if (source === "mcp") return "mcp";
|
||||
if (source === "web" || source === "cli") return "rest";
|
||||
return "other";
|
||||
}
|
||||
|
||||
/**
|
||||
* 能力当前的授权状态(服务端按 automation_grant 判定):
|
||||
* - read_only:read / preview,只看 scope,不需要授权;
|
||||
* - granted:有覆盖它的有效持续授权(自动来源仍受自动化总开关约束);
|
||||
* - needs_approval:没有持续授权,需要单次审批(或控制台手动执行);
|
||||
* - agent_only:只在 Agent 会话内可用(agent.* 能力,例如派生子会话)。
|
||||
*/
|
||||
export const CapabilityAuthState = z.enum(["read_only", "granted", "needs_approval", "agent_only"]);
|
||||
export type CapabilityAuthState = z.infer<typeof CapabilityAuthState>;
|
||||
|
||||
export const CapabilityStatsQuery = z.object({
|
||||
days: z.coerce.number().int().min(1).max(90).default(7),
|
||||
});
|
||||
export type CapabilityStatsQuery = z.infer<typeof CapabilityStatsQuery>;
|
||||
|
||||
const CallerCounts = z.object({
|
||||
agent: z.number().int().nonnegative(),
|
||||
mcp: z.number().int().nonnegative(),
|
||||
rest: z.number().int().nonnegative(),
|
||||
other: z.number().int().nonnegative(),
|
||||
});
|
||||
|
||||
/** 单个能力在统计窗口内的调用汇总(来自 audit_event 的 capability.invoke / capability.denied) */
|
||||
export const CapabilityStatsItem = z.object({
|
||||
capabilityId: z.string(),
|
||||
calls: z.number().int().nonnegative(),
|
||||
/** outcome = error / invalid */
|
||||
failures: z.number().int().nonnegative(),
|
||||
/** 授权或白名单拒绝 */
|
||||
denied: z.number().int().nonnegative(),
|
||||
avgDurationMs: z.number().nonnegative().nullable(),
|
||||
lastCalledAt: Timestamp.nullable(),
|
||||
lastOutcome: z.string().nullable(),
|
||||
lastCaller: CapabilityCaller.nullable(),
|
||||
byCaller: CallerCounts,
|
||||
});
|
||||
export type CapabilityStatsItem = z.infer<typeof CapabilityStatsItem>;
|
||||
|
||||
export const CapabilityStatsResponse = z.object({
|
||||
days: z.number().int(),
|
||||
since: Timestamp,
|
||||
items: z.array(CapabilityStatsItem),
|
||||
});
|
||||
export type CapabilityStatsResponse = z.infer<typeof CapabilityStatsResponse>;
|
||||
|
||||
/** 能力清单(给工具页与会话页工具卡片):描述符 + 工具名 + JSON Schema + 授权状态 */
|
||||
export const CapabilityToolInfo = CapabilityDescriptor.extend({
|
||||
toolName: z.string(),
|
||||
inputJsonSchema: z.record(z.string(), z.unknown()),
|
||||
outputJsonSchema: z.record(z.string(), z.unknown()),
|
||||
authState: CapabilityAuthState,
|
||||
/** 覆盖它的有效持续授权数 */
|
||||
activeGrants: z.number().int().nonnegative(),
|
||||
/** 能否经 MCP 暴露(agent.* 只在 Agent 会话内) */
|
||||
mcpExposed: z.boolean(),
|
||||
});
|
||||
export type CapabilityToolInfo = z.infer<typeof CapabilityToolInfo>;
|
||||
|
||||
export const CapabilityToolsResponse = z.object({
|
||||
/** 自动化总开关:关着时 execute 类能力对自动来源(Agent / MCP / API Key)一律拒绝 */
|
||||
automationEnabled: z.boolean(),
|
||||
readOnlyReconcile: z.boolean(),
|
||||
items: z.array(CapabilityToolInfo),
|
||||
});
|
||||
export type CapabilityToolsResponse = z.infer<typeof CapabilityToolsResponse>;
|
||||
|
||||
/** MCP 服务端配置(mcp_server_config 单行) */
|
||||
export const McpServerConfig = z.object({
|
||||
enabled: z.boolean(),
|
||||
/** 允许连接的 API Key;null = 所有未撤销的 Key */
|
||||
allowedApiKeyIds: z.array(Id).nullable(),
|
||||
updatedAt: Timestamp.nullable(),
|
||||
});
|
||||
export type McpServerConfig = z.infer<typeof McpServerConfig>;
|
||||
|
||||
export const McpServerConfigUpdate = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
allowedApiKeyIds: z.array(Id).max(200).nullable(),
|
||||
})
|
||||
.partial();
|
||||
export type McpServerConfigUpdate = z.infer<typeof McpServerConfigUpdate>;
|
||||
|
||||
export const McpSessionInfo = z.object({
|
||||
/** 会话 ID 前 8 位(完整 ID 等同会话凭据,不回显) */
|
||||
idPrefix: z.string(),
|
||||
apiKeyId: z.string(),
|
||||
apiKeyName: z.string(),
|
||||
clientName: z.string().nullable(),
|
||||
createdAt: Timestamp,
|
||||
lastSeenAt: Timestamp,
|
||||
toolCalls: z.number().int().nonnegative(),
|
||||
});
|
||||
export type McpSessionInfo = z.infer<typeof McpSessionInfo>;
|
||||
|
||||
export const McpRecentCall = z.object({
|
||||
at: Timestamp,
|
||||
capabilityId: z.string(),
|
||||
outcome: z.string(),
|
||||
actorId: z.string().nullable(),
|
||||
durationMs: z.number().nullable(),
|
||||
errorCode: z.string().nullable(),
|
||||
});
|
||||
export type McpRecentCall = z.infer<typeof McpRecentCall>;
|
||||
|
||||
export const McpServerStatus = McpServerConfig.extend({
|
||||
/** 相对站点根的端点路径(/api/v1/mcp) */
|
||||
path: z.string(),
|
||||
transport: z.literal("streamable-http"),
|
||||
serverName: z.string(),
|
||||
serverVersion: z.string(),
|
||||
/** 每个 API Key 每分钟的工具调用上限 */
|
||||
rateLimitPerMinute: z.number().int().positive(),
|
||||
/** 每个 API Key 同时保持的会话上限 */
|
||||
maxSessionsPerKey: z.number().int().positive(),
|
||||
sessions: z.array(McpSessionInfo),
|
||||
recentCalls: z.array(McpRecentCall),
|
||||
/** 常见 Key 组合能看到的工具数(read / read + organize.execute / admin) */
|
||||
toolsByScope: z.object({
|
||||
read: z.number().int().nonnegative(),
|
||||
readExecute: z.number().int().nonnegative(),
|
||||
admin: z.number().int().nonnegative(),
|
||||
}),
|
||||
});
|
||||
export type McpServerStatus = z.infer<typeof McpServerStatus>;
|
||||
|
||||
/** MCP 工具可见性:能力 scope 被 Key 覆盖;没有 organize.execute(或 admin)时只给 read / preview 类 */
|
||||
export function mcpToolVisible(
|
||||
d: Pick<CapabilityDescriptor, "id" | "kind" | "scope">,
|
||||
scopes: readonly string[],
|
||||
): boolean {
|
||||
if (d.id.startsWith("agent.")) return false;
|
||||
const has = (s: ApiScope) =>
|
||||
scopes.includes("admin") || scopes.includes(s) || (s === "organize.read" && scopes.includes("organize.execute"));
|
||||
if (!has(d.scope)) return false;
|
||||
const readish: readonly CapabilityKind[] = ["read", "preview"];
|
||||
return readish.includes(d.kind) || has("organize.execute");
|
||||
}
|
||||
@@ -132,6 +132,8 @@ const INTERNAL_TABLES = [
|
||||
// W9-I 进程心跳(0034):服务端内部,经 /system/health 的专用 DTO 暴露
|
||||
"ProcessHeartbeat",
|
||||
"ProcessHeartbeatSample",
|
||||
// AG-1 MCP 服务端单行配置(0039):经 /mcp/status 与 /mcp/config 的专用 DTO 暴露
|
||||
"McpServerConfig",
|
||||
// C1-B 恢复码 / Passkey / WebAuthn 挑战(0037):服务端内部,经 contracts/auth.ts 的专用 DTO 暴露
|
||||
"AdminRecoveryCode",
|
||||
"AdminPasskey",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Kysely } from "kysely";
|
||||
import { dropTables, execDdl } from "../src/migration-helpers.ts";
|
||||
|
||||
// AG-1 对外 MCP 服务端与能力统计:
|
||||
// - mcp_server_config:单行配置(启用开关、允许连接的 API Key;null = 所有未撤销的 Key)。
|
||||
// - 能力调用统计直接汇总 audit_event(capability.invoke / capability.denied 覆盖读写全部能力,
|
||||
// capability_invocation 只记有副作用的调用);补一条按能力 + 时间的部分索引。
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(
|
||||
db,
|
||||
`
|
||||
create table mcp_server_config (
|
||||
id integer primary key default 1 check (id = 1),
|
||||
enabled boolean not null default true,
|
||||
allowed_api_key_ids text[],
|
||||
updated_by text,
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
insert into mcp_server_config (id) values (1);
|
||||
|
||||
create index if not exists audit_event_capability_idx on audit_event (target_id, at)
|
||||
where target_type = 'capability';
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(
|
||||
db,
|
||||
`drop index if exists audit_event_capability_idx;
|
||||
${dropTables(["mcp_server_config"])}`,
|
||||
);
|
||||
}
|
||||
@@ -868,6 +868,14 @@ export interface MediaAssetTable {
|
||||
download_task_id: string | null;
|
||||
}
|
||||
|
||||
export interface McpServerConfigTable {
|
||||
id: Generated<number>;
|
||||
enabled: Generated<boolean>;
|
||||
allowed_api_key_ids: string[] | null;
|
||||
updated_by: string | null;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface MediaEntityTable {
|
||||
id: Generated<string>;
|
||||
kind: MediaEntityKind;
|
||||
@@ -1623,6 +1631,7 @@ export interface DB {
|
||||
library_root: LibraryRootTable;
|
||||
login_attempt: LoginAttemptTable;
|
||||
media_asset: MediaAssetTable;
|
||||
mcp_server_config: McpServerConfigTable;
|
||||
media_entity: MediaEntityTable;
|
||||
media_server_instance: MediaServerInstanceTable;
|
||||
metadata_field: MetadataFieldTable;
|
||||
@@ -1772,6 +1781,9 @@ export type LoginAttemptUpdate = Updateable<LoginAttemptTable>;
|
||||
export type MediaAssetRow = Selectable<MediaAssetTable>;
|
||||
export type NewMediaAsset = Insertable<MediaAssetTable>;
|
||||
export type MediaAssetUpdate = Updateable<MediaAssetTable>;
|
||||
export type McpServerConfigRow = Selectable<McpServerConfigTable>;
|
||||
export type NewMcpServerConfig = Insertable<McpServerConfigTable>;
|
||||
export type McpServerConfigUpdate = Updateable<McpServerConfigTable>;
|
||||
export type MediaEntityRow = Selectable<MediaEntityTable>;
|
||||
export type NewMediaEntity = Insertable<MediaEntityTable>;
|
||||
export type MediaEntityUpdate = Updateable<MediaEntityTable>;
|
||||
|
||||
@@ -109,6 +109,8 @@ const EXPECTED_TABLES = [
|
||||
// W9-I 进程心跳(0034)
|
||||
"process_heartbeat",
|
||||
"process_heartbeat_sample",
|
||||
// AG-1 MCP 服务端配置(0039)
|
||||
"mcp_server_config",
|
||||
// C1-B 恢复码 / Passkey / WebAuthn 挑战(0037)
|
||||
"admin_recovery_code",
|
||||
"admin_passkey",
|
||||
@@ -132,6 +134,8 @@ const NON_ENTITY_TABLES = [
|
||||
// 进程心跳:instance_id 主键 / 只追加的采样
|
||||
"process_heartbeat",
|
||||
"process_heartbeat_sample",
|
||||
// 单行配置(id = 1)
|
||||
"mcp_server_config",
|
||||
// 一次性凭据:只记创建 / 用掉的时间
|
||||
"admin_recovery_code",
|
||||
"webauthn_challenge",
|
||||
|
||||
Reference in new issue
Block a user