feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,60 @@
|
||||
import { z } from "zod"
|
||||
import { mcpCatalogResponseSchema, mcpConnectionsSchema, mcpSettingsSchema, mcpPolicyPreviewResponseSchema, mcpPollStorageCapacitySchema, type McpConnectionCreate } from "@mmapp/contracts/mcp"
|
||||
import { requestJson } from "./http-client"
|
||||
import { downloadHumanArtifact } from "./mcp-artifact"
|
||||
|
||||
export const mcpRecordsSchema = z.object({ items: z.array(z.record(z.string(), z.unknown())), total: z.number().int(), nextCursor: z.string().nullable() })
|
||||
export const mcpOverviewSchema = z.object({ connections: z.number(), calls: z.number(), errors: z.number(), approvals: z.number(), running: z.number(), enabled: z.boolean(), observedAt: z.string(), pollStorageCapacity:mcpPollStorageCapacitySchema.default({status:"unavailable"}) })
|
||||
const deviceCatalogSchema = z.object({ servers: z.array(z.object({ id: z.number(), name: z.string(), host: z.string(), enabled: z.boolean() })), resources: z.array(z.object({ id: z.string(), title: z.string(), read: z.boolean(), create: z.boolean(), update: z.boolean(), delete: z.boolean(), fields: z.array(z.object({ key: z.string(), type: z.enum(["string", "number", "boolean"]), options: z.array(z.string()).optional(), min: z.number().optional(), max: z.number().optional(), maxLength: z.number().optional(), requiredOnCreate: z.boolean() })) })) })
|
||||
export function mcpApi(base: string) {
|
||||
const get = async <T>(path: string, schema: z.ZodType<T>, signal?: AbortSignal): Promise<T> => schema.parse(await requestJson(base, "/api/mcp" + path, { signal }))
|
||||
const send = async <T>(path: string, body: unknown, schema: z.ZodType<T>, method = "POST"): Promise<T> => schema.parse(await requestJson(base, "/api/mcp" + path, { method, body: JSON.stringify(body) }))
|
||||
return {
|
||||
downloadArtifact: (receipt: unknown, signal?: AbortSignal) => downloadHumanArtifact(base, receipt, signal),
|
||||
coverage: (signal?: AbortSignal) => get("/coverage", z.object({ version: z.literal(1), routes: z.array(z.object({ method: z.string(), path: z.string(), status: z.enum(["mediated", "excluded", "pending"]), operations: z.array(z.string()), reason: z.string() })) }), signal),
|
||||
uploadRestoreArchive: async (file: File) => z.object({ archiveRef: z.string(), sha256: z.string(), sizeBytes: z.number(), expiresAt: z.string() }).parse(await requestJson(base, "/api/mcp/database/archives", { method: "POST", headers: { "Content-Type": "application/octet-stream" }, body: file })),
|
||||
restorePlan: (connectionId: string, archiveRef: string, expectedSha256: string) => send("/database/plan", { connectionId, archiveRef, expectedSha256 }, z.object({ planId: z.string(), state: z.string() })),
|
||||
restoreReceipt: (id: string) => get(`/database/receipts/${encodeURIComponent(id)}`, z.record(z.string(), z.unknown())),
|
||||
managementChannels: (signal?: AbortSignal) => get("/management-channels", z.object({ channels: z.array(z.record(z.string(), z.unknown())) }), signal),
|
||||
registerManagement: (input: unknown) => send("/management-channels", input, z.object({ id: z.string(), serverId: z.number(), expiresAt: z.string(), verification: z.string(), independence: z.string() })),
|
||||
revokeManagement: (id: string) => send(`/management-channels/${id}`, {}, z.object({ ok: z.literal(true) }), "DELETE"),
|
||||
recoverOperation: (id: string) => send(`/operations/${id}/recover`, {}, z.object({ planId: z.string(), state: z.string() })),
|
||||
checkRoutePath: (id: string, candidateId: string, proofPurpose: "community" | "control-path" = "community") => send(`/routing-profiles/${id}/check`, { candidateId, proofPurpose }, z.record(z.string(), z.unknown())),
|
||||
reconcileOperation: (id: string) => send(`/operations/${id}/reconcile`, {}, z.record(z.string(), z.unknown())),
|
||||
deviceCatalog: (connectionId: string, signal?: AbortSignal) => get("/devices/catalog?" + new URLSearchParams({ connectionId }), deviceCatalogSchema, signal),
|
||||
deviceRows: (connectionId: string, serverId: string, resource: string, offset = 0, signal?: AbortSignal) => get(`/devices/${encodeURIComponent(serverId)}/${encodeURIComponent(resource)}?` + new URLSearchParams({ connectionId, offset: String(offset) }), z.object({ items: z.array(z.record(z.string(), z.unknown())), total: z.number(), nextOffset: z.number().nullable() }), signal),
|
||||
devicePlan: (connectionId: string, serverId: string, resource: string, action: "create" | "update" | "delete", id: string | undefined, fields: Record<string, unknown>) => send(`/devices/${encodeURIComponent(serverId)}/${encodeURIComponent(resource)}/plan`, { connectionId, action, id, fields }, z.object({ planId: z.string(), state: z.string() })),
|
||||
revalidate: (id: string) => send("/connections/" + id + "/revalidate", {}, mcpConnectionsSchema.shape.connections.element),
|
||||
apply: (id: string) => send("/plans/" + id + "/apply", {}, z.object({ id: z.string(), state: z.string() })),
|
||||
profilePlan: (input: unknown) => send("/routing-profiles/plan", input, z.object({ planId: z.string(), state: z.string() })),
|
||||
catalog: (signal?: AbortSignal) => get("/catalog", mcpCatalogResponseSchema, signal),
|
||||
connections: (signal?: AbortSignal) => get("/connections", mcpConnectionsSchema, signal),
|
||||
overview: (signal?: AbortSignal) => get("/overview", mcpOverviewSchema, signal),
|
||||
settings: (signal?: AbortSignal) => get("/settings", mcpSettingsSchema, signal),
|
||||
records: (kind: "requests" | "plans" | "operations" | "audit" | "drift", params: URLSearchParams, signal?: AbortSignal) => get("/" + kind + "?" + params, mcpRecordsSchema, signal),
|
||||
create: (input: McpConnectionCreate) => send("/connections", input, z.object({ token: z.string(), connection: mcpConnectionsSchema.shape.connections.element })),
|
||||
update: (id: string, input: McpConnectionCreate) => send("/connections/" + id, input, mcpConnectionsSchema.shape.connections.element, "PUT"),
|
||||
status: (id: string, status: "active" | "paused" | "revoked") => send(`/connections/${id}/status`, { status }, z.object({ ok: z.literal(true) })),
|
||||
rotate: (id: string) => send(`/connections/${id}/rotate`, {}, z.object({ token: z.string() })),
|
||||
check: (id: string) => send(`/connections/${id}/check`, {}, z.object({ ok: z.boolean(), checks: z.record(z.string(), z.boolean()), checkedAt: z.string(), protocolProbe: z.object({ status: z.enum(["passed", "failed", "skipped"]), elapsedMs: z.number(), reason: z.string().optional(), credentialType: z.literal("temporary-no-operations"), clientCredentialsVerified: z.literal(false) }).optional() })),
|
||||
grants: (id: string, signal?: AbortSignal) => get(`/connections/${id}/grants`, z.object({ grants: z.array(z.object({ id: z.string(), grant: z.record(z.string(), z.unknown()), revoked_at: z.string().nullable() })) }), signal),
|
||||
grant: (id: string, grant: unknown) => send(`/connections/${id}/grants`, grant, z.object({ token: z.string(), expiresAt: z.string() })),
|
||||
revokeGrant: (id: string) => send(`/grants/${id}`, {}, z.object({ ok: z.literal(true) }), "DELETE"),
|
||||
profiles: (signal?: AbortSignal) => get("/routing-profiles", z.object({ profiles: z.array(z.record(z.string(), z.unknown())) }), signal),
|
||||
pauseProfile: (id: string) => send(`/routing-profiles/${id}/pause`, {}, z.object({ ok: z.literal(true) })),
|
||||
decision: (id: string, approve: boolean) => send(`/plans/${id}/decision`, { approve }, z.object({ ok: z.literal(true) })),
|
||||
saveSettings: (settings: unknown) => send("/settings", settings, mcpSettingsSchema, "PUT"),
|
||||
pause: () => send("/pause", {}, z.object({ ok: z.literal(true) })),
|
||||
stopAutomation: () => send("/stop-automation", {}, z.object({ ok: z.literal(true) })),
|
||||
preview: (input: unknown) => send("/policy/preview", input, mcpPolicyPreviewResponseSchema),
|
||||
clients: (signal?: AbortSignal) => get("/oauth/clients", z.object({ clients: z.array(z.object({ id: z.string(), name: z.string(), redirect_uris: z.array(z.string()), enabled: z.boolean() })) }), signal),
|
||||
createClient: (name: string, redirectUris: string[]) => send("/oauth/clients", { name, redirectUris }, z.object({ clientId: z.string() })),
|
||||
disableClient: (id: string) => send(`/oauth/clients/${id}`, {}, z.object({ ok: z.literal(true) }), "DELETE"),
|
||||
consentRequest: (id: string, signal?: AbortSignal) => get("/oauth/requests/" + id, z.object({ id: z.string(), name: z.string(), redirect_uri: z.string(), scope: z.string() }), signal),
|
||||
consent: (requestId: string, connectionId: string, approve: boolean) => send("/oauth/consent", { requestId, connectionId, approve }, z.object({ redirectUrl: z.string().url() })),
|
||||
protections: (signal?: AbortSignal) => get("/protections", z.object({ protections: z.array(z.object({ id: z.string(), object_ref: z.object({ kind: z.string(), id: z.string(), serverId: z.number().optional(), protection: z.enum(["deny", "human-approval-required"]) }), reason: z.string() })) }), signal),
|
||||
protect: (object: unknown, reason: string) => send("/protections", { object, reason }, z.object({ id: z.string() })),
|
||||
removeProtection: async (id: string) => requestJson(base, "/api/mcp/protections/" + id, { method: "DELETE" }),
|
||||
}
|
||||
}
|
||||
export type McpApi = ReturnType<typeof mcpApi>
|
||||
Reference in new issue
Block a user