Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois) - Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real - Documentacao: specs, plano, runbook de problemas reais e export do workflow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* scripts/diagnose-zpro-list.ts
|
||||
*
|
||||
* Script CLI temporário e seguro para descobrir o contrato real do
|
||||
* endpoint:
|
||||
*
|
||||
* GET /v2/api/external/{ApiID}/appointment/list
|
||||
*
|
||||
* Faz apenas UMA requisição GET de leitura (page=1, limit=5), com timeout
|
||||
* de 15s, sem retry e sem modificar nenhum dado. Imprime a resposta
|
||||
* sanitizada e um resumo estrutural.
|
||||
*
|
||||
* O script nunca imprime token, header Authorization, stack trace ou
|
||||
* dados pessoais completos dos pacientes.
|
||||
*/
|
||||
|
||||
import { loadSchedulingConfig } from "../src/config/env.config.js";
|
||||
import {
|
||||
buildAppointmentListUrl,
|
||||
fetchAppointmentList,
|
||||
NetworkRequestError,
|
||||
RequestTimeoutError,
|
||||
} from "../src/diagnostics/client.js";
|
||||
import { buildResponseReport } from "../src/diagnostics/response.js";
|
||||
import { sanitizeText } from "../src/diagnostics/sanitize.js";
|
||||
import type { StructureSummary } from "../src/diagnostics/analyze.js";
|
||||
|
||||
function list(values: string[]): string {
|
||||
return values.length > 0 ? values.join(", ") : "(nenhum)";
|
||||
}
|
||||
|
||||
function classifyHttpStatus(status: number): string {
|
||||
if (status === 401 || status === 403) {
|
||||
return `Erro de autenticação (status ${status}). Verifique ZPRO_API_TOKEN.`;
|
||||
}
|
||||
if (status === 404) {
|
||||
return `ApiID inválido ou recurso não encontrado (status ${status}). Verifique ZPRO_API_ID.`;
|
||||
}
|
||||
return `Status HTTP ${status} (esperado 2xx).`;
|
||||
}
|
||||
|
||||
function printStructureSummary(summary: StructureSummary): void {
|
||||
console.log(`Tipo da raiz: ${summary.rootType}`);
|
||||
|
||||
if (summary.rootType === "array") {
|
||||
console.log(`Quantidade de itens no array: ${summary.arrayItemCount}`);
|
||||
console.log(`Chaves do primeiro item: ${list(summary.firstItemKeys)}`);
|
||||
} else if (summary.rootType === "object") {
|
||||
console.log(`Chaves na raiz: ${list(summary.rootKeys)}`);
|
||||
}
|
||||
|
||||
console.log("Tipos dos campos:");
|
||||
for (const [key, type] of Object.entries(summary.fieldTypes)) {
|
||||
console.log(` ${key}: ${type}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Possíveis campos de paginação: ${list(summary.possiblePaginationFields)}`,
|
||||
);
|
||||
console.log(
|
||||
`Possíveis campos de identificação: ${list(summary.possibleIdFields)}`,
|
||||
);
|
||||
console.log(`Possíveis campos de data: ${list(summary.possibleDateFields)}`);
|
||||
console.log(
|
||||
`Possíveis campos de status: ${list(summary.possibleStatusFields)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function main(): Promise<number> {
|
||||
let config;
|
||||
try {
|
||||
config = loadSchedulingConfig();
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Falha ao carregar a configuração.";
|
||||
console.error(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const secrets = [config.apiToken];
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetchAppointmentList(config);
|
||||
} catch (error) {
|
||||
if (error instanceof RequestTimeoutError) {
|
||||
console.error("Timeout ao consultar a API do Z-PRO (15s).");
|
||||
} else if (error instanceof NetworkRequestError) {
|
||||
console.error("Falha de rede ao consultar a API do Z-PRO.");
|
||||
} else {
|
||||
console.error("Erro inesperado ao consultar a API do Z-PRO.");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
console.log("=== Informações da requisição ===");
|
||||
console.log("Método: GET");
|
||||
console.log(`URL: ${buildAppointmentListUrl(config)}`);
|
||||
console.log(`Status: ${response.status}`);
|
||||
console.log(`Content-Type: ${response.contentType ?? "(ausente)"}`);
|
||||
console.log(`Duração: ${response.durationMs}ms`);
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
console.log("");
|
||||
console.log("=== Erro HTTP ===");
|
||||
console.log(classifyHttpStatus(response.status));
|
||||
if (response.bodyText) {
|
||||
console.log("");
|
||||
console.log("Corpo da resposta (primeiros 500 caracteres, sanitizado):");
|
||||
console.log(sanitizeText(response.bodyText, secrets));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const report = buildResponseReport(response.bodyText, secrets);
|
||||
|
||||
if (!report.isJson) {
|
||||
console.log("");
|
||||
console.log("=== A resposta não é JSON ===");
|
||||
console.log("Conteúdo (primeiros 500 caracteres, sanitizado):");
|
||||
console.log(report.sanitizedText);
|
||||
return 1;
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log("=== Estrutura da resposta ===");
|
||||
console.log(report.sanitizedText);
|
||||
|
||||
if (report.structure) {
|
||||
console.log("");
|
||||
console.log("=== Resumo estrutural ===");
|
||||
printStructureSummary(report.structure);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
main()
|
||||
.then((code) => {
|
||||
process.exit(code);
|
||||
})
|
||||
.catch(() => {
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user