next-finance-mcp 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +9 -2
- package/dist/client.js +57 -11
- package/dist/index.js +30 -10
- package/dist/qr-server.d.ts +18 -0
- package/dist/qr-server.js +160 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -113,8 +113,15 @@ export declare function atualizarOpenFinance(nomeConta: string): Promise<{
|
|
|
113
113
|
sucesso: boolean;
|
|
114
114
|
mensagem: string;
|
|
115
115
|
}>;
|
|
116
|
-
/**
|
|
117
|
-
export declare function
|
|
116
|
+
/** Abre janela local com QR do Pluggy para conta com MFA. */
|
|
117
|
+
export declare function iniciarOpenFinanceQR(nomeConta: string): Promise<{
|
|
118
|
+
conta: string;
|
|
119
|
+
status: string;
|
|
120
|
+
mensagem: string;
|
|
121
|
+
}>;
|
|
122
|
+
/** Solicita o extrato OF (faz pull do banco para o período) — POST JSON web.
|
|
123
|
+
* Período default: últimos 30 dias. */
|
|
124
|
+
export declare function obterExtratoOpenFinance(nomeConta: string, dataInicioOpt?: string, dataFimOpt?: string): Promise<{
|
|
118
125
|
conta: string;
|
|
119
126
|
sucesso: boolean;
|
|
120
127
|
mensagem: string;
|
package/dist/client.js
CHANGED
|
@@ -646,6 +646,18 @@ export async function listarTiposConta() {
|
|
|
646
646
|
const tipos = arr.map(t => ({ nome: String(t["Nome"] ?? t["Descricao"] ?? "") })).filter(t => t.nome);
|
|
647
647
|
return { tipos };
|
|
648
648
|
}
|
|
649
|
+
// ── Helpers de datas padrão ──────────────────────────────────────────────────
|
|
650
|
+
// Default consistente em toda a API: últimos 30 dias terminando hoje.
|
|
651
|
+
function isoDate(d) { return d.toISOString().slice(0, 10); }
|
|
652
|
+
function defaultsUltimos30Dias(opts) {
|
|
653
|
+
const hoje = new Date();
|
|
654
|
+
const dataFim = opts.dataFim ?? isoDate(hoje);
|
|
655
|
+
// 30 dias atrás = hoje - 30 (inclusive)
|
|
656
|
+
const trintaDiasAtras = new Date(hoje);
|
|
657
|
+
trintaDiasAtras.setDate(trintaDiasAtras.getDate() - 30);
|
|
658
|
+
const dataInicio = opts.dataInicio ?? isoDate(trintaDiasAtras);
|
|
659
|
+
return { dataInicio, dataFim };
|
|
660
|
+
}
|
|
649
661
|
/** Resolve um nome de conta → IdConta interno, exigindo match único (não expõe ID). */
|
|
650
662
|
async function resolverIdConta(nome) {
|
|
651
663
|
const { contas } = await fetchContas();
|
|
@@ -709,9 +721,49 @@ export async function atualizarOpenFinance(nomeConta) {
|
|
|
709
721
|
}
|
|
710
722
|
return { conta: nome, sucesso: true, mensagem: r.text?.trim() || "Sincronização iniciada." };
|
|
711
723
|
}
|
|
712
|
-
/**
|
|
713
|
-
|
|
724
|
+
/** Pega o accessToken do Pluggy para iniciar o widget Connect. */
|
|
725
|
+
async function obterPluggyAccessToken(itemId) {
|
|
726
|
+
const r = await webGet(`/ConciliacaoAjax/ObterOpenFinanceAccessToken?itemId=${encodeURIComponent(itemId)}`);
|
|
727
|
+
if (r.status !== 200)
|
|
728
|
+
throw new Error(`Falha ao obter access token Pluggy (HTTP ${r.status}).`);
|
|
729
|
+
return r.text.trim().replace(/^"|"$/g, "");
|
|
730
|
+
}
|
|
731
|
+
/** Abre janela local com QR do Pluggy para conta com MFA. */
|
|
732
|
+
export async function iniciarOpenFinanceQR(nomeConta) {
|
|
733
|
+
const { contas } = await fetchContas();
|
|
734
|
+
const ativas = contas.filter(isContaAtiva);
|
|
735
|
+
const termo = nomeConta.trim().toLowerCase();
|
|
736
|
+
const matches = ativas.filter(c => String(c["Nome"] ?? c["NomeConta"] ?? "").toLowerCase().includes(termo));
|
|
737
|
+
if (matches.length === 0)
|
|
738
|
+
throw new Error(`Conta "${nomeConta}" não encontrada.`);
|
|
739
|
+
const exata = matches.find(c => String(c["Nome"] ?? c["NomeConta"] ?? "").toLowerCase() === termo);
|
|
740
|
+
const conta = exata ?? (matches.length === 1 ? matches[0] : null);
|
|
741
|
+
if (!conta)
|
|
742
|
+
throw new Error(`"${nomeConta}" corresponde a ${matches.length} contas. Seja mais específico.`);
|
|
743
|
+
const itemId = String(conta["OpenFinanceId"] ?? "");
|
|
744
|
+
if (!itemId)
|
|
745
|
+
throw new Error(`Conta "${nomeConta}" não tem vínculo Open Finance.`);
|
|
746
|
+
const nomeFmt = String(conta["Nome"] ?? "");
|
|
747
|
+
const banco = String(conta["NomeBanco"] ?? "Banco");
|
|
748
|
+
const accessToken = await obterPluggyAccessToken(itemId);
|
|
749
|
+
const { abrirQrPluggy } = await import("./qr-server.js");
|
|
750
|
+
const result = await abrirQrPluggy(accessToken, itemId, banco, nomeFmt);
|
|
751
|
+
if (result.status === "success") {
|
|
752
|
+
return { conta: nomeFmt, status: "success", mensagem: "Autenticação Open Finance concluída. Use 'obter_extrato_open_finance' para puxar o extrato." };
|
|
753
|
+
}
|
|
754
|
+
if (result.status === "timeout") {
|
|
755
|
+
return { conta: nomeFmt, status: "timeout", mensagem: "Tempo esgotado (5 min). Tente novamente." };
|
|
756
|
+
}
|
|
757
|
+
if (result.status === "error") {
|
|
758
|
+
return { conta: nomeFmt, status: "error", mensagem: `Erro no widget Pluggy: ${result.error ?? "desconhecido"}` };
|
|
759
|
+
}
|
|
760
|
+
return { conta: nomeFmt, status: "closed", mensagem: "Janela fechada antes de concluir a autenticação." };
|
|
761
|
+
}
|
|
762
|
+
/** Solicita o extrato OF (faz pull do banco para o período) — POST JSON web.
|
|
763
|
+
* Período default: últimos 30 dias. */
|
|
764
|
+
export async function obterExtratoOpenFinance(nomeConta, dataInicioOpt, dataFimOpt) {
|
|
714
765
|
const { id, nome } = await resolverIdConta(nomeConta);
|
|
766
|
+
const { dataInicio, dataFim } = defaultsUltimos30Dias({ dataInicio: dataInicioOpt, dataFim: dataFimOpt });
|
|
715
767
|
// Endpoint web real (verificado): POST application/json com DataInicio/DataFim em ISO datetime
|
|
716
768
|
const body = JSON.stringify({
|
|
717
769
|
DataInicio: `${dataInicio}T00:00:00`,
|
|
@@ -819,9 +871,7 @@ function slimDespesa(d, comItens) {
|
|
|
819
871
|
return result;
|
|
820
872
|
}
|
|
821
873
|
export async function buscarDespesas(opts = {}) {
|
|
822
|
-
const
|
|
823
|
-
const dataInicio = opts.dataInicio ?? `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-01`;
|
|
824
|
-
const dataFim = opts.dataFim ?? today.toISOString().slice(0, 10);
|
|
874
|
+
const { dataInicio, dataFim } = defaultsUltimos30Dias(opts);
|
|
825
875
|
// API exige formato ISO completo com hora
|
|
826
876
|
const raw = await apiPost("/api/Despesa/Filtrar", {
|
|
827
877
|
DataInicio: dataInicio + "T00:00:00.000Z",
|
|
@@ -855,9 +905,7 @@ export async function buscarDespesas(opts = {}) {
|
|
|
855
905
|
};
|
|
856
906
|
}
|
|
857
907
|
export async function buscarItensDespesa(opts = {}) {
|
|
858
|
-
const
|
|
859
|
-
const dataInicio = opts.dataInicio ?? `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-01`;
|
|
860
|
-
const dataFim = opts.dataFim ?? today.toISOString().slice(0, 10);
|
|
908
|
+
const { dataInicio, dataFim } = defaultsUltimos30Dias(opts);
|
|
861
909
|
// Resolve nome do fornecedor → ID interno (nunca exposto)
|
|
862
910
|
let pessoaId;
|
|
863
911
|
if (opts.nomeFornecedor?.trim()) {
|
|
@@ -982,9 +1030,7 @@ const TIPOS_CONTA_MOVIMENTO = new Set([
|
|
|
982
1030
|
"contas em cobrança", "contas em cobranca",
|
|
983
1031
|
]);
|
|
984
1032
|
export async function buscarLancamentos(opts) {
|
|
985
|
-
const
|
|
986
|
-
const dataInicio = opts.dataInicio ?? `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-01`;
|
|
987
|
-
const dataFim = opts.dataFim ?? today.toISOString().slice(0, 10);
|
|
1033
|
+
const { dataInicio, dataFim } = defaultsUltimos30Dias(opts);
|
|
988
1034
|
const pagina = Math.max(1, opts.pagina ?? 1);
|
|
989
1035
|
const limite = Math.min(200, Math.max(1, opts.limite ?? 50));
|
|
990
1036
|
// Resolve nome da conta → ID interno (nunca exposto ao usuário)
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import * as client from "./client.js";
|
|
10
10
|
import { openLoginUI } from "./login-server.js";
|
|
11
|
-
const server = new Server({ name: "next-finance-mcp", version: "0.9.
|
|
11
|
+
const server = new Server({ name: "next-finance-mcp", version: "0.9.15" }, { capabilities: { tools: {} } });
|
|
12
12
|
// ── Ferramentas ─────────────────────────────────────────────────────────────
|
|
13
13
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
14
14
|
tools: [
|
|
@@ -100,7 +100,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
100
100
|
type: "object",
|
|
101
101
|
properties: {
|
|
102
102
|
nome_conta: { type: "string", description: "Nome da conta (opcional — omitir busca em todas as contas, ex: 'Nubank', 'Inter')" },
|
|
103
|
-
data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão:
|
|
103
|
+
data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão: 30 dias atrás)" },
|
|
104
104
|
data_fim: { type: "string", description: "Data fim YYYY-MM-DD (padrão: hoje)" },
|
|
105
105
|
plano_de_contas: { type: "string", description: "Filtra por categoria/plano de contas (ex: 'Restaurante')" },
|
|
106
106
|
centro_de_custo: { type: "string", description: "Filtra por centro de custo" },
|
|
@@ -157,20 +157,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
157
157
|
required: ["nome_conta"],
|
|
158
158
|
},
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
name: "iniciar_open_finance_qr",
|
|
162
|
+
description: "Abre uma janela local no navegador com o widget Pluggy Connect para autenticar uma conta Open Finance que exige MFA (ex: Inter, que pede leitura de QR Code no Super App). Após o usuário concluir a autenticação, a janela fecha sozinha e a conta fica pronta para 'obter_extrato_open_finance'.",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
nome_conta: { type: "string", description: "Nome da conta (ex: 'Inter CC 651549')" },
|
|
167
|
+
},
|
|
168
|
+
required: ["nome_conta"],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
160
171
|
{
|
|
161
172
|
name: "obter_extrato_open_finance",
|
|
162
|
-
description: "Solicita ao banco (via Open Finance) o extrato
|
|
163
|
-
"Diferente de 'atualizar_open_finance' (que só pega o mais recente),
|
|
164
|
-
"
|
|
165
|
-
"para
|
|
173
|
+
description: "Solicita ao banco (via Open Finance) o extrato de uma conta no período. " +
|
|
174
|
+
"Diferente de 'atualizar_open_finance' (que só pega o mais recente), permite " +
|
|
175
|
+
"especificar um intervalo de datas. Default: últimos 30 dias. Após sucesso, " +
|
|
176
|
+
"a conciliação fica pendente no NEXT — finalize no app para os lançamentos aparecerem em 'buscar_lancamentos'.",
|
|
166
177
|
inputSchema: {
|
|
167
178
|
type: "object",
|
|
168
179
|
properties: {
|
|
169
180
|
nome_conta: { type: "string", description: "Nome da conta (ex: 'Inter CC 651549')" },
|
|
170
|
-
data_inicio: { type: "string", description: "Data início YYYY-MM-DD" },
|
|
171
|
-
data_fim: { type: "string", description: "Data fim YYYY-MM-DD" },
|
|
181
|
+
data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão: 30 dias atrás)" },
|
|
182
|
+
data_fim: { type: "string", description: "Data fim YYYY-MM-DD (padrão: hoje)" },
|
|
172
183
|
},
|
|
173
|
-
required: ["nome_conta"
|
|
184
|
+
required: ["nome_conta"],
|
|
174
185
|
},
|
|
175
186
|
},
|
|
176
187
|
{
|
|
@@ -182,7 +193,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
182
193
|
inputSchema: {
|
|
183
194
|
type: "object",
|
|
184
195
|
properties: {
|
|
185
|
-
data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão:
|
|
196
|
+
data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão: 30 dias atrás)" },
|
|
186
197
|
data_fim: { type: "string", description: "Data fim YYYY-MM-DD (padrão: hoje)" },
|
|
187
198
|
nome_fornecedor: { type: "string", description: "Filtra por nome do fornecedor (ex: 'Supermercados BH', 'Epa')" },
|
|
188
199
|
busca: { type: "string", description: "Filtra por número do documento ou tipo" },
|
|
@@ -377,6 +388,15 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
377
388
|
return walErr;
|
|
378
389
|
return ok(await client.atualizarOpenFinance(args["nome_conta"]));
|
|
379
390
|
}
|
|
391
|
+
case "iniciar_open_finance_qr": {
|
|
392
|
+
const authErr = requireAuth();
|
|
393
|
+
if (authErr)
|
|
394
|
+
return authErr;
|
|
395
|
+
const walErr = requireWallet();
|
|
396
|
+
if (walErr)
|
|
397
|
+
return walErr;
|
|
398
|
+
return ok(await client.iniciarOpenFinanceQR(args["nome_conta"]));
|
|
399
|
+
}
|
|
380
400
|
case "obter_extrato_open_finance": {
|
|
381
401
|
const authErr = requireAuth();
|
|
382
402
|
if (authErr)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Servidor HTTP local para exibir o QR Code do Pluggy Connect.
|
|
3
|
+
* Usado quando uma conta Open Finance exige MFA (ex: Inter).
|
|
4
|
+
*
|
|
5
|
+
* Fluxo:
|
|
6
|
+
* 1. MCP busca o accessToken do Pluggy via ConciliacaoAjax/ObterOpenFinanceAccessToken
|
|
7
|
+
* 2. Inicia servidor HTTP local com página que carrega o widget Pluggy Connect
|
|
8
|
+
* 3. Abre o browser na URL local
|
|
9
|
+
* 4. Widget Pluggy renderiza o QR; usuário escaneia no app do banco
|
|
10
|
+
* 5. Após sucesso, a página POSTa /done de volta para o servidor → MCP encerra e retorna
|
|
11
|
+
*/
|
|
12
|
+
export interface QrSessionResult {
|
|
13
|
+
status: "success" | "error" | "closed" | "timeout";
|
|
14
|
+
itemId?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Abre o widget Pluggy no browser local e espera o usuário concluir o MFA. */
|
|
18
|
+
export declare function abrirQrPluggy(accessToken: string, itemId: string, banco: string, conta: string, timeoutMs?: number): Promise<QrSessionResult>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Servidor HTTP local para exibir o QR Code do Pluggy Connect.
|
|
3
|
+
* Usado quando uma conta Open Finance exige MFA (ex: Inter).
|
|
4
|
+
*
|
|
5
|
+
* Fluxo:
|
|
6
|
+
* 1. MCP busca o accessToken do Pluggy via ConciliacaoAjax/ObterOpenFinanceAccessToken
|
|
7
|
+
* 2. Inicia servidor HTTP local com página que carrega o widget Pluggy Connect
|
|
8
|
+
* 3. Abre o browser na URL local
|
|
9
|
+
* 4. Widget Pluggy renderiza o QR; usuário escaneia no app do banco
|
|
10
|
+
* 5. Após sucesso, a página POSTa /done de volta para o servidor → MCP encerra e retorna
|
|
11
|
+
*/
|
|
12
|
+
import * as http from "http";
|
|
13
|
+
import * as os from "os";
|
|
14
|
+
import { exec } from "child_process";
|
|
15
|
+
function openBrowser(url) {
|
|
16
|
+
const platform = os.platform();
|
|
17
|
+
const cmd = platform === "darwin" ? `open "${url}"` :
|
|
18
|
+
platform === "win32" ? `start "" "${url}"` :
|
|
19
|
+
`xdg-open "${url}"`;
|
|
20
|
+
exec(cmd, (err) => {
|
|
21
|
+
if (err)
|
|
22
|
+
console.error("Não foi possível abrir o browser:", err.message);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function buildHtml(accessToken, itemId, banco, conta) {
|
|
26
|
+
return `<!DOCTYPE html>
|
|
27
|
+
<html lang="pt-br">
|
|
28
|
+
<head>
|
|
29
|
+
<meta charset="utf-8">
|
|
30
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
31
|
+
<title>Open Finance — ${conta}</title>
|
|
32
|
+
<style>
|
|
33
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
34
|
+
body {
|
|
35
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
36
|
+
background: #f0f2f5; min-height: 100vh;
|
|
37
|
+
display: flex; align-items: center; justify-content: center;
|
|
38
|
+
}
|
|
39
|
+
.card {
|
|
40
|
+
background: #fff; border-radius: 12px;
|
|
41
|
+
box-shadow: 0 4px 24px rgba(0,0,0,.10);
|
|
42
|
+
padding: 32px; width: 480px; text-align: center;
|
|
43
|
+
}
|
|
44
|
+
h1 { color: #1a73e8; font-size: 20px; margin-bottom: 8px; }
|
|
45
|
+
.sub { color: #666; font-size: 13px; margin-bottom: 24px; }
|
|
46
|
+
.status {
|
|
47
|
+
padding: 16px; border-radius: 8px; margin-top: 20px;
|
|
48
|
+
font-size: 14px; line-height: 1.5;
|
|
49
|
+
}
|
|
50
|
+
.status.info { background: #e8f0fe; color: #1a73e8; }
|
|
51
|
+
.status.success { background: #e6f4ea; color: #137333; }
|
|
52
|
+
.status.error { background: #fce8e6; color: #c5221f; }
|
|
53
|
+
.btn {
|
|
54
|
+
margin-top: 16px; padding: 10px 20px; background: #1a73e8; color: #fff;
|
|
55
|
+
border: none; border-radius: 6px; font-size: 14px; cursor: pointer;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
<div class="card">
|
|
61
|
+
<h1>Conectar via Open Finance</h1>
|
|
62
|
+
<p class="sub"><strong>${banco}</strong> — ${conta}</p>
|
|
63
|
+
<div id="status" class="status info">Carregando o widget do Pluggy…</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<script src="https://cdn.pluggy.ai/pluggy-connect/v2.10.0/pluggy-connect.js"></script>
|
|
67
|
+
<script>
|
|
68
|
+
const ACCESS_TOKEN = ${JSON.stringify(accessToken)};
|
|
69
|
+
const ITEM_ID = ${JSON.stringify(itemId)};
|
|
70
|
+
const statusEl = document.getElementById("status");
|
|
71
|
+
function setStatus(text, kind) { statusEl.textContent = text; statusEl.className = "status " + kind; }
|
|
72
|
+
function notifyServer(payload) {
|
|
73
|
+
fetch("/done", { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify(payload) }).catch(() => {});
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const pc = new PluggyConnect({
|
|
77
|
+
connectToken: ACCESS_TOKEN,
|
|
78
|
+
updateItem: ITEM_ID,
|
|
79
|
+
includeSandbox: false,
|
|
80
|
+
onSuccess: (data) => {
|
|
81
|
+
setStatus("✅ Autenticação concluída! Você já pode fechar esta janela.", "success");
|
|
82
|
+
notifyServer({ status: "success", itemId: data.item?.id ?? ITEM_ID });
|
|
83
|
+
},
|
|
84
|
+
onError: (err) => {
|
|
85
|
+
setStatus("❌ Erro: " + (err.message || JSON.stringify(err)), "error");
|
|
86
|
+
notifyServer({ status: "error", error: err.message || String(err) });
|
|
87
|
+
},
|
|
88
|
+
onClose: () => {
|
|
89
|
+
setStatus("Widget fechado. Se concluiu a autenticação, pode fechar esta janela.", "info");
|
|
90
|
+
notifyServer({ status: "closed" });
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
pc.init();
|
|
94
|
+
setStatus("Aponte a câmera do app do banco para o QR Code que vai aparecer.", "info");
|
|
95
|
+
} catch (e) {
|
|
96
|
+
setStatus("Erro ao iniciar widget Pluggy: " + e.message, "error");
|
|
97
|
+
notifyServer({ status: "error", error: e.message });
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
</body>
|
|
101
|
+
</html>`;
|
|
102
|
+
}
|
|
103
|
+
/** Abre o widget Pluggy no browser local e espera o usuário concluir o MFA. */
|
|
104
|
+
export function abrirQrPluggy(accessToken, itemId, banco, conta, timeoutMs = 5 * 60 * 1000) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
let resolved = false;
|
|
107
|
+
const resolveOnce = (r) => {
|
|
108
|
+
if (resolved)
|
|
109
|
+
return;
|
|
110
|
+
resolved = true;
|
|
111
|
+
try {
|
|
112
|
+
server.close();
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
resolve(r);
|
|
117
|
+
};
|
|
118
|
+
const server = http.createServer((req, res) => {
|
|
119
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
|
|
120
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
121
|
+
res.end(buildHtml(accessToken, itemId, banco, conta));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (req.method === "POST" && req.url === "/done") {
|
|
125
|
+
let body = "";
|
|
126
|
+
req.on("data", chunk => body += chunk);
|
|
127
|
+
req.on("end", () => {
|
|
128
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
129
|
+
res.end(`{"ok":true}`);
|
|
130
|
+
try {
|
|
131
|
+
const data = JSON.parse(body);
|
|
132
|
+
if (data.status === "success")
|
|
133
|
+
resolveOnce({ status: "success", itemId: data.itemId });
|
|
134
|
+
else if (data.status === "error")
|
|
135
|
+
resolveOnce({ status: "error", error: data.error });
|
|
136
|
+
else if (data.status === "closed") {
|
|
137
|
+
// Closed sem success: mantemos servidor por mais alguns segundos caso o user reabra
|
|
138
|
+
// Mas pra simplificar, vamos fechar mesmo
|
|
139
|
+
setTimeout(() => resolveOnce({ status: "closed" }), 1000);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch { /* ignora payload inválido */ }
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
res.writeHead(404);
|
|
147
|
+
res.end("Not found");
|
|
148
|
+
});
|
|
149
|
+
server.listen(0, "127.0.0.1", () => {
|
|
150
|
+
const addr = server.address();
|
|
151
|
+
if (!addr || typeof addr === "string") {
|
|
152
|
+
resolveOnce({ status: "error", error: "Falha ao iniciar servidor local" });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const url = `http://127.0.0.1:${addr.port}/`;
|
|
156
|
+
openBrowser(url);
|
|
157
|
+
});
|
|
158
|
+
const timer = setTimeout(() => resolveOnce({ status: "timeout" }), timeoutMs);
|
|
159
|
+
});
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-finance-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.15",
|
|
4
4
|
"mcpName": "io.github.paivapiovesan/next-finance",
|
|
5
5
|
"description": "MCP Server para o NEXT Finance (finance.net.br) — login via browser, listagem de carteiras, contas e lançamentos",
|
|
6
6
|
"type": "module",
|