Ctrl K
ComunidadGuías…

lib/agent-readiness.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { McpServer, McpTool, OpenApiEndpoint } from "@/lib/types";

export type AgentReadinessCheck = {
  label: string;
  status: "passed" | "warning" | "missing";
  detail: string;
};

export type AgentReadinessReport = {
  score: number;
  grade: "production-ready" | "demo-ready" | "needs-review";
  checks: AgentReadinessCheck[];
  advantages: string[];
  nextActions: string[];
};

function hasSecurityRequirement(endpoint: OpenApiEndpoint) {
  const security = endpoint.security_requirements ?? endpoint.security;
  if (endpoint.requires_auth === true) return true;
  if (!security) return false;
  if (Array.isArray(security)) return security.length > 0;
  if (typeof security === "object") return Object.keys(security).length > 0;
  return Boolean(security);
}

function mappedTools(tools: McpTool[], endpointMap: OpenApiEndpoint[]) {
  return tools.filter((tool) =>
    endpointMap.some((endpoint) =>
      endpoint.tool_name === tool.name ||
      endpoint.operation_id === tool.name ||
      (endpoint.method === tool.method && endpoint.path === tool.path)
    )
  ).length;
}

function allToolsHaveAgentProfiles(tools: McpTool[]) {
  return tools.length > 0 && tools.every((tool) => Boolean(tool.x_astrail && tool.annotations));
}

function toolsWithExamples(tools: McpTool[]) {
  return tools.filter((tool) =>
    tool.x_astrail?.example_arguments &&
    Object.keys(tool.x_astrail.example_arguments).length > 0
  ).length;
}

function compressedComplexTools(tools: McpTool[]) {
  return tools.filter((tool) => tool.x_astrail?.complexity?.compressed).length;
}

function check(label: string, passed: boolean, detail: string, warning = false): AgentReadinessCheck {
  return {
    label,
    status: passed ? "passed" : warning ? "warning" : "missing",
    detail,
  };
}

export function buildAgentReadinessReport(server: McpServer): AgentReadinessReport {
  const tools = server.tools_json ?? [];
  const endpointMap = server.endpoint_map ?? [];
  const mappedToolCount = mappedTools(tools, endpointMap);
  const authRequiredCount = endpointMap.filter(hasSecurityRequirement).length;
  const executableCount = endpointMap.filter((endpoint) =>
    ["GET", "POST"].includes(endpoint.method) &&
    Boolean(endpoint.base_url) &&
    !hasSecurityRequirement(endpoint)
  ).length;
  const exampleCount = toolsWithExamples(tools);
  const compressedCount = compressedComplexTools(tools);

  const checks: AgentReadinessCheck[] = [
    check(
      "MCP surface",
      tools.length > 0,
      tools.length > 0 ? `${tools.length} tools exposed through tools/list.` : "No tools have been generated yet."
    ),
    check(
      "Endpoint map",
      endpointMap.length > 0 && mappedToolCount > 0,
      endpointMap.length > 0
        ? `${mappedToolCount}/${tools.length} tools are mapped to deterministic runtime metadata.`
        : "No endpoint map is stored for tools/call execution."
    ),
    check(
      "Hosted runtime",
      Boolean(server.hosted_endpoint),
      server.hosted_endpoint ? `Hosted at ${server.hosted_endpoint}.` : "No hosted endpoint is attached."
    ),
    check(
      "Safe execution",
      executableCount > 0 || authRequiredCount > 0,
      executableCount > 0
        ? `${executableCount} unauthenticated GET/POST tool(s) can execute through native fetch.`
        : authRequiredCount > 0
          ? `${authRequiredCount} tool(s) correctly stop at auth_required until credentials are configured.`
          : "No deterministic execution path detected.",
      endpointMap.length > 0
    ),
    check(
      "Agent annotations",
      allToolsHaveAgentProfiles(tools),
      allToolsHaveAgentProfiles(tools)
        ? "Every tool includes MCP annotations and Astrail risk/auth metadata."
        : "Some tools are missing risk/auth/example metadata."
    ),
    check(
      "Example arguments",
      exampleCount > 0,
      exampleCount > 0
        ? `${exampleCount} tool(s) include generated example arguments.`
        : "No generated examples are attached to tool metadata.",
      tools.length > 0
    ),
    check(
      "Parameter compression",
      compressedCount > 0 || tools.every((tool) => (tool.x_astrail?.complexity?.parameter_count ?? 0) <= 10),
      compressedCount > 0
        ? `${compressedCount} complex tool(s) use compact body mode instead of huge function signatures.`
        : "No large request schemas detected; tools remain compact.",
      tools.length > 0
    ),
    check(
      "Auth boundary",
      endpointMap.length > 0,
      authRequiredCount > 0
        ? `${authRequiredCount} endpoint(s) declare upstream auth requirements.`
        : "No auth-required operations detected in this endpoint map.",
      false
    ),
  ];

  const score = Math.round((checks.reduce((sum, item) => {
    if (item.status === "passed") return sum + 1;
    if (item.status === "warning") return sum + 0.5;
    return sum;
  }, 0) / checks.length) * 100);

  const grade = score >= 85 ? "production-ready" : score >= 60 ? "demo-ready" : "needs-review";
  const missing = checks.filter((item) => item.status !== "passed");

  return {
    score,
    grade,
    checks,
    advantages: [
      "Hosted MCP endpoint, not just generated source files.",
      "Deterministic endpoint_map runtime avoids eval and arbitrary generated-code execution.",
      "Agent risk/auth/example metadata travels through tools/list and bundles.",
      "Complex OpenAPI request bodies are compressed into usable body objects for agents.",
      "Worker export, credential injection, runtime proof, logs, and bundles compound around the generated server.",
    ],
    nextActions: missing.length === 0
      ? ["Run a live tools/call test and connect credentials for private provider endpoints."]
      : missing.slice(0, 3).map((item) => item.detail),
  };
}