diff --git a/packages/payments/celcoin/README.md b/packages/payments/celcoin/README.md index 6f900c0b..5c8b315e 100644 --- a/packages/payments/celcoin/README.md +++ b/packages/payments/celcoin/README.md @@ -64,13 +64,13 @@ Add to `.cursor/mcp.json` or `.vscode/mcp.json`: | `create_pix_cobv` | Create a Pix due charge (cobv) — boleto-like Pix with due date | | `lookup_pix_dict` | Lookup a Pix DICT key — resolves a Pix key to account holder + bank info | | `create_pix_devolution` | Create a Pix devolução (refund) — refund a received Pix transaction | -| `cancel_boleto` | Cancel an issued boleto by transactionId | -| `read_barcode` | Read a boleto / concessionária barcode (digitable line) — returns due date, amount, beneficiary | -| `pay_bill` | Pay a bill (boleto bancário or concessionária) by barcode / digitable line | +| `cancel_boleto` | Cancel a boleto issued via the bill-issuance product by id | +| `read_barcode` | Authorize (consult) a boleto / concessionária barcode before paying — returns transactionId, amount, totalUpdated, dueDate | +| `pay_bill` | Confirm payment of a previously authorized bill — pass the read_barcode transactionId as transactionIdAuthorize | | `get_statement` | Get account statement (extrato) for a date range | | `list_topup_providers` | List telecom top-up providers (operadoras) available for recargas | -| `create_boleto` | Create a boleto payment via Celcoin | -| `get_boleto` | Get boleto details by transaction ID | +| `create_boleto` | Issue a boleto via the bill-issuance product | +| `get_boleto` | Get an issued boleto's details by id | | `create_transfer` | Create a bank transfer (TED/DOC) via Celcoin | | `get_balance` | Get account balance at Celcoin | | `list_banks` | List available banks in Brazil (ISPB codes) | @@ -82,7 +82,7 @@ Celcoin uses OAuth2 client credentials. The server automatically manages token r ## Sandbox / Testing -Celcoin provides a sandbox at `sandbox-api.celcoin.com.br`. Set `CELCOIN_SANDBOX=true` to use it. +Celcoin provides a sandbox at `sandbox.openfinance.celcoin.dev`. Set `CELCOIN_SANDBOX=true` to use it. ### Get your credentials diff --git a/packages/payments/celcoin/package.json b/packages/payments/celcoin/package.json index ba74d4bd..a7f027df 100644 --- a/packages/payments/celcoin/package.json +++ b/packages/payments/celcoin/package.json @@ -1,6 +1,6 @@ { "name": "@codespar/mcp-celcoin", - "version": "0.2.2", + "version": "0.2.3", "description": "MCP server for Celcoin — Pix, boleto, transfers, bill payments, top-ups", "type": "module", "main": "./dist/index.js", diff --git a/packages/payments/celcoin/server.json b/packages/payments/celcoin/server.json index 758a73a6..85ffd4cf 100644 --- a/packages/payments/celcoin/server.json +++ b/packages/payments/celcoin/server.json @@ -7,12 +7,12 @@ "source": "github", "subfolder": "packages/payments/celcoin" }, - "version": "0.2.2", + "version": "0.2.3", "packages": [ { "registryType": "npm", "identifier": "@codespar/mcp-celcoin", - "version": "0.2.2", + "version": "0.2.3", "transport": { "type": "stdio" }, diff --git a/packages/payments/celcoin/src/__tests__/contract.test.ts b/packages/payments/celcoin/src/__tests__/contract.test.ts new file mode 100644 index 00000000..aedd25f6 --- /dev/null +++ b/packages/payments/celcoin/src/__tests__/contract.test.ts @@ -0,0 +1,99 @@ +import { it, expect, beforeAll, vi } from "vitest"; +import { + loadContractEnv, + describeContract, + parseToolResult, +} from "../../../../../test-utils/contract.js"; + +// --- Capture the tools/call handler; do NOT mock fetch (real network) --- +let callToolHandler: Function; + +vi.mock("@modelcontextprotocol/sdk/server/index.js", () => { + class FakeServer { + constructor() {} + setRequestHandler(schema: any, handler: Function) { + if (JSON.stringify(schema).includes("tools/call")) callToolHandler = handler; + } + connect() { + return Promise.resolve(); + } + } + return { Server: FakeServer }; +}); + +vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({ + StdioServerTransport: class {}, +})); + +await loadContractEnv(); + +async function call(name: string, args: Record = {}) { + if (!callToolHandler) { + throw new Error( + "callToolHandler not registered — did beforeAll (vi.resetModules + import ../index.js) complete?", + ); + } + const result = await callToolHandler({ params: { name, arguments: args } }); + return parseToolResult(result); +} + +// This block is SKIPPED in CI (no CELCOIN_CLIENT_ID). Run locally with real +// sandbox credentials in /.env: +// CELCOIN_CLIENT_ID=... +// CELCOIN_CLIENT_SECRET=... +// CELCOIN_SANDBOX=true +// CELCOIN_TEST_DIGITABLE= (optional) +// It codifies the real authorize -> confirm handshake against the sandbox host +// (sandbox.openfinance.celcoin.dev). +describeContract("mcp-celcoin", "CELCOIN_CLIENT_ID", () => { + // A ficha-de-compensação digitable line (type 2). Override with a barcode + // valid for your sandbox account via CELCOIN_TEST_DIGITABLE. + const DIGITABLE = + process.env.CELCOIN_TEST_DIGITABLE || + "34191790010104351004791020150008291070026000"; + + beforeAll(async () => { + process.env.CELCOIN_SANDBOX = "true"; + vi.resetModules(); + callToolHandler = undefined as any; + await import("../index.js"); + }); + + it( + "read_barcode -> pay_bill: authorize then confirm handshake", + async () => { + // Step 1: authorize (consult). Real sandbox returns a transactionId that + // opens the reservation window, or a structured error for an unpayable + // barcode. Either way the route must resolve (no 404 on the old GET). + const authorized = await call("read_barcode", { + barcode: DIGITABLE, + barcodeType: 2, + }); + expect(authorized.text).not.toMatch(/404/); + + // If authorize did not yield a transactionId (e.g. the sample barcode is + // not payable in this sandbox account), we have still proven the + // authorize route resolves; there is nothing to confirm. + const transactionId = (authorized.json as any)?.transactionId; + const totalUpdated = (authorized.json as any)?.totalUpdated; + if (!transactionId) { + expect(authorized.isError).toBe(true); + return; + } + + // Step 2: confirm. The confirm MUST carry transactionIdAuthorize + billData; + // without them Celcoin answers errorCode 44 "Valor nao permitido". + const paid = await call("pay_bill", { + barcode: DIGITABLE, + barcodeType: 2, + transactionIdAuthorize: String(transactionId), + amount: totalUpdated, + originalValue: totalUpdated, + cpfcnpj: process.env.CELCOIN_TEST_CPFCNPJ || "12345678909", + }); + // The confirm route must resolve; the errorCode-44 body bug must not recur. + expect(paid.text).not.toMatch(/errorCode.*44|Valor nao permitido/i); + }, + 30000, + ); +}); diff --git a/packages/payments/celcoin/src/__tests__/index.test.ts b/packages/payments/celcoin/src/__tests__/index.test.ts index 3557b3da..1243c6b0 100644 --- a/packages/payments/celcoin/src/__tests__/index.test.ts +++ b/packages/payments/celcoin/src/__tests__/index.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; let listToolsHandler: Function; let callToolHandler: Function; @@ -23,15 +23,36 @@ process.env.CELCOIN_CLIENT_SECRET = "test-secret"; const mockFetch = vi.fn(); global.fetch = mockFetch as any; -beforeEach(async () => { +// The first fetch a Celcoin call makes is always POST /v5/token. Queue the OAuth +// token, then the endpoint's own response, and assert on the LAST fetch call. +const tokenResponse = () => ({ ok: true, json: () => Promise.resolve({ access_token: "tok", expires_in: 3600 }) }); +const okJson = (body: unknown) => ({ ok: true, json: () => Promise.resolve(body) }); + +function lastCall() { + return mockFetch.mock.calls[mockFetch.mock.calls.length - 1]; +} +function lastBody() { + return JSON.parse(lastCall()[1].body); +} + +async function loadServer() { vi.resetModules(); listToolsHandler = undefined as any; callToolHandler = undefined as any; + await import("../index.js"); +} + +beforeEach(async () => { + delete process.env.CELCOIN_SANDBOX; mockFetch.mockReset(); global.fetch = mockFetch as any; - // Mock OAuth token response for all requests - mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({ access_token: "tok", expires_in: 3600 }) }); - await import("../index.js"); + // Default: every fetch returns a token (enough for the "18 tools" listing test). + mockFetch.mockResolvedValue(tokenResponse()); + await loadServer(); +}); + +afterEach(() => { + delete process.env.CELCOIN_SANDBOX; }); describe("mcp-celcoin", () => { @@ -41,14 +62,172 @@ describe("mcp-celcoin", () => { }); it("should call correct API endpoint for get_balance", async () => { - // First call is OAuth token, second is the actual request - mockFetch - .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ access_token: "tok", expires_in: 3600 }) }) - .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ balance: 1000 }) }); + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ balance: 1000 })); await callToolHandler({ params: { name: "get_balance", arguments: {} } }); - const lastCall = mockFetch.mock.calls[mockFetch.mock.calls.length - 1]; - expect(lastCall[0]).toContain("/v5/merchant/balance"); + expect(lastCall()[0]).toContain("/v5/merchant/balance"); + }); + + describe("read_barcode (authorize)", () => { + it("POSTs to /v5/transactions/billpayments/authorize with barCode {type, digitable}", async () => { + mockFetch.mockReset(); + mockFetch + .mockResolvedValueOnce(tokenResponse()) + .mockResolvedValueOnce(okJson({ transactionId: 9001, totalUpdated: 150.5, allowChangeValue: false })); + + await callToolHandler({ + params: { name: "read_barcode", arguments: { barcode: "846700000017", barcodeType: 1 } }, + }); + + const [url, opts] = lastCall(); + expect(url).toContain("/v5/transactions/billpayments/authorize"); + expect(url).not.toContain("?barCode="); + expect(opts.method).toBe("POST"); + expect(lastBody()).toMatchObject({ barCode: { type: 1, digitable: "846700000017" } }); + }); + + it("defaults barcode type to 2 (ficha de compensação) when omitted", async () => { + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ transactionId: 1 })); + + await callToolHandler({ params: { name: "read_barcode", arguments: { barcode: "34191790010104351004791020150008291070026000" } } }); + + expect(lastBody().barCode.type).toBe(2); + }); + }); + + describe("pay_bill (confirm)", () => { + it("carries transactionIdAuthorize, cpfcnpj, billData and externalNSU (errorCode-44 regression guard)", async () => { + mockFetch.mockReset(); + mockFetch + .mockResolvedValueOnce(tokenResponse()) + .mockResolvedValueOnce(okJson({ transactionId: 9001, status: "CONFIRMED" })); + + await callToolHandler({ + params: { + name: "pay_bill", + arguments: { + barcode: "846700000017", + barcodeType: 1, + transactionIdAuthorize: "9001", + amount: 150.5, + originalValue: 150.5, + dueDate: "2026-07-10", + cpfcnpj: "12345678909", + externalNSU: 42, + }, + }, + }); + + const [url, opts] = lastCall(); + expect(url).toContain("/v5/transactions/billpayments"); + expect(url).not.toContain("/authorize"); + expect(opts.method).toBe("POST"); + const body = lastBody(); + expect(body).toMatchObject({ + transactionIdAuthorize: "9001", + cpfcnpj: "12345678909", + externalNSU: 42, + barCode: { type: 1, digitable: "846700000017" }, + billData: { value: 150.5, originalValue: 150.5 }, + dueDate: "2026-07-10", + }); + // The old bare `amount` field must be gone — its absence is the fix for + // Celcoin errorCode 44 "Valor nao permitido". + expect(body.amount).toBeUndefined(); + }); + }); + + describe("create_boleto (issuance)", () => { + it("POSTs to /billissuance/v1/bill", async () => { + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ transactionId: "b1" })); + + await callToolHandler({ + params: { + name: "create_boleto", + arguments: { amount: 99, dueDate: "2026-08-01", payerName: "Ana", payerDocument: "12345678909" }, + }, + }); + + const [url, opts] = lastCall(); + expect(url).toContain("/billissuance/v1/bill"); + expect(url).not.toContain("/v5/transactions/billpayments/bankslip"); + expect(opts.method).toBe("POST"); + }); + }); + + describe("get_boleto / cancel_boleto (issuance)", () => { + it("get_boleto GETs /billissuance/v1/bill/:id", async () => { + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ id: "b1" })); + + await callToolHandler({ params: { name: "get_boleto", arguments: { transactionId: "b1" } } }); + + const [url, opts] = lastCall(); + expect(url).toContain("/billissuance/v1/bill/b1"); + expect(opts.method).toBe("GET"); + }); + + it("cancel_boleto DELETEs /billissuance/v1/bill/:id", async () => { + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ status: "CANCELLED" })); + + await callToolHandler({ params: { name: "cancel_boleto", arguments: { transactionId: "b1" } } }); + + const [url, opts] = lastCall(); + expect(url).toContain("/billissuance/v1/bill/b1"); + expect(opts.method).toBe("DELETE"); + }); + }); + + describe("sandbox host", () => { + it("uses sandbox.openfinance.celcoin.dev (not the dead sandbox-api host) when CELCOIN_SANDBOX=true", async () => { + process.env.CELCOIN_SANDBOX = "true"; + await loadServer(); + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ balance: 1 })); + + await callToolHandler({ params: { name: "get_balance", arguments: {} } }); + + const url = lastCall()[0]; + expect(url).toContain("sandbox.openfinance.celcoin.dev"); + expect(url).not.toContain("sandbox-api.celcoin.com.br"); + }); + + it("uses the production host when CELCOIN_SANDBOX is unset", async () => { + mockFetch.mockReset(); + mockFetch.mockResolvedValueOnce(tokenResponse()).mockResolvedValueOnce(okJson({ balance: 1 })); + + await callToolHandler({ params: { name: "get_balance", arguments: {} } }); + + const url = lastCall()[0]; + expect(url).toContain("api-sec.celcoin.com.br"); + expect(url).not.toContain("sandbox"); + }); + }); + + describe("error handling", () => { + it("returns isError true on a 400 confirm response", async () => { + mockFetch.mockReset(); + mockFetch + .mockResolvedValueOnce(tokenResponse()) + .mockResolvedValueOnce({ ok: false, status: 400, text: () => Promise.resolve('{"errorCode":"44"}') }); + + const result = await callToolHandler({ + params: { name: "pay_bill", arguments: { barcode: "1", amount: 1, transactionIdAuthorize: "1" } }, + }); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain("400"); + }); + + it("returns isError true for an unknown tool", async () => { + const result = await callToolHandler({ params: { name: "nonexistent", arguments: {} } }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain("Unknown tool"); + }); }); }); diff --git a/packages/payments/celcoin/src/index.ts b/packages/payments/celcoin/src/index.ts index 3ca6b53b..89486e2e 100644 --- a/packages/payments/celcoin/src/index.ts +++ b/packages/payments/celcoin/src/index.ts @@ -11,11 +11,11 @@ * - create_pix_cobv: Create a Pix due charge (cobv) with vencimento * - lookup_pix_dict: Lookup a Pix DICT key (resolve key to account holder) * - create_pix_devolution: Create a Pix devolução (refund) - * - cancel_boleto: Cancel an issued boleto - * - read_barcode: Read a boleto / concessionária barcode (digitable line) - * - pay_bill: Pay a bill (boleto / concessionária) by barcode - * - create_boleto: Create a boleto payment - * - get_boleto: Get boleto details + * - cancel_boleto: Cancel a boleto issued via the bill-issuance product + * - read_barcode: Authorize (consult) a boleto / concessionária barcode before paying + * - pay_bill: Confirm payment of a previously authorized bill (boleto / concessionária) + * - create_boleto: Issue a boleto via the bill-issuance product + * - get_boleto: Get an issued boleto's details * - create_transfer: Create a bank transfer (TED) * - get_balance: Get account balance * - get_statement: Get account statement (extrato) @@ -41,7 +41,7 @@ import { const CLIENT_ID = process.env.CELCOIN_CLIENT_ID || ""; const CLIENT_SECRET = process.env.CELCOIN_CLIENT_SECRET || ""; const BASE_URL = process.env.CELCOIN_SANDBOX === "true" - ? "https://sandbox-api.celcoin.com.br" + ? "https://sandbox.openfinance.celcoin.dev" : "https://api-sec.celcoin.com.br"; let accessToken = ""; @@ -92,7 +92,7 @@ const MANAGED_TIER_HINT = "This open-source CodeSpar server calls the provider's API directly. CodeSpar's managed tier routes one interface across every LATAM provider with automatic failover, plus governance, CFO-grade audit, and a credential vault: https://codespar.dev/agents (npx -y @codespar/mcp serve)."; const server = new Server( - { name: "mcp-celcoin", version: "0.2.1" }, + { name: "mcp-celcoin", version: "0.2.3" }, { capabilities: { tools: {} }, instructions: MANAGED_TIER_HINT } ); @@ -208,28 +208,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ }, { name: "read_barcode", - description: "Read a boleto / concessionária barcode (digitable line) — returns due date, amount, beneficiary", + description: "Authorize (consult) a boleto / concessionária barcode before paying — returns transactionId, amount, totalUpdated, allowChangeValue, dueDate. The transactionId opens a reservation window; pass it to pay_bill as transactionIdAuthorize.", inputSchema: { type: "object", properties: { barcode: { type: "string", description: "Digitable line (linha digitável) — 47 or 48 digits" }, + barcodeType: { type: "number", enum: [1, 2], description: "Barcode category: 1 = concessionária/utility (arrecadação), 2 = bank slip (ficha de compensação). Defaults to 2." }, }, required: ["barcode"], }, }, { name: "pay_bill", - description: "Pay a bill (boleto bancário or concessionária) by barcode / digitable line", + description: "Confirm payment of a bill (boleto bancário or concessionária) previously authorized with read_barcode. Requires transactionIdAuthorize from the authorize step.", inputSchema: { type: "object", properties: { barcode: { type: "string", description: "Digitable line (linha digitável)" }, - amount: { type: "number", description: "Amount in BRL" }, + barcodeType: { type: "number", enum: [1, 2], description: "Barcode category: 1 = concessionária/utility, 2 = bank slip. Defaults to 2." }, + transactionIdAuthorize: { type: "string", description: "transactionId returned by read_barcode (the authorize step). Required so Celcoin matches the confirm to the reservation." }, + amount: { type: "number", description: "Amount in BRL. Must equal the authorize totalUpdated unless allowChangeValue was true." }, + originalValue: { type: "number", description: "Original face value of the bill (from authorize). Optional." }, + valueWithDiscount: { type: "number", description: "Value after discount, if any. Optional." }, + valueWithAdditional: { type: "number", description: "Value including interest/fine, if any. Optional." }, dueDate: { type: "string", description: "Due date (YYYY-MM-DD)" }, - payerDocument: { type: "string", description: "Payer CPF/CNPJ" }, - payerName: { type: "string", description: "Payer name" }, + cpfcnpj: { type: "string", description: "Payer CPF/CNPJ (documento do pagador)" }, + externalNSU: { type: "number", description: "Caller-side transaction identifier (optional)." }, + externalTerminal: { type: "string", description: "Caller-side terminal / client identifier (optional)." }, }, - required: ["barcode", "amount"], + required: ["barcode", "amount", "transactionIdAuthorize"], }, }, { @@ -403,19 +410,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/pix/v1/devolution", payload), null, 2) }] }; } case "cancel_boleto": - return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("DELETE", `/v5/transactions/billpayments/bankslip/${args?.transactionId}`), null, 2) }] }; + // Cancel an issued boleto under the bill-issuance product (billissuance), + // not the bill-payment product. The v5/transactions/billpayments/* routes + // are for *paying* a third-party slip and have no cancel-issued semantics. + return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("DELETE", `/billissuance/v1/bill/${args?.transactionId}`), null, 2) }] }; case "read_barcode": { - const params = new URLSearchParams({ barCode: String(args?.barcode ?? "") }); - return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("GET", `/v5/transactions/billpayments?${params}`), null, 2) }] }; + // Authorize (consult) step: POST /v5/transactions/billpayments/authorize. + // Opens a reservation keyed by the returned transactionId; that value is + // passed to pay_bill as transactionIdAuthorize to confirm the payment. + // The old GET .../billpayments?barCode= route returned 404. + const payload = { + barCode: { + type: args?.barcodeType ?? 2, + digitable: args?.barcode, + }, + }; + return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/v5/transactions/billpayments/authorize", payload), null, 2) }] }; } case "pay_bill": { + // Confirm step: POST /v5/transactions/billpayments. The prior body sent a + // bare `amount` and no transactionIdAuthorize, which Celcoin rejected with + // errorCode 44 "Valor nao permitido". The confirm must carry the + // transactionIdAuthorize from read_barcode plus the resolved billData. const payload = { - barCode: args?.barcode, - amount: args?.amount, + externalNSU: args?.externalNSU, + externalTerminal: args?.externalTerminal, + cpfcnpj: args?.cpfcnpj, + transactionIdAuthorize: args?.transactionIdAuthorize, + billData: { + value: args?.amount, + originalValue: args?.originalValue ?? args?.amount, + valueWithDiscount: args?.valueWithDiscount, + valueWithAdditional: args?.valueWithAdditional, + }, + barCode: { + type: args?.barcodeType ?? 2, + digitable: args?.barcode, + }, dueDate: args?.dueDate, - payer: args?.payerDocument - ? { document: args?.payerDocument, name: args?.payerName } - : undefined, }; return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/v5/transactions/billpayments", payload), null, 2) }] }; } @@ -433,9 +465,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("GET", `/v5/transactions/topups/providers${params}`), null, 2) }] }; } case "create_boleto": - return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/v5/transactions/billpayments/bankslip", args), null, 2) }] }; + // Issue a new boleto via the bill-issuance product. The prior + // /v5/transactions/billpayments/bankslip route returned 404 — that + // namespace pays third-party slips, it does not issue them. + return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/billissuance/v1/bill", args), null, 2) }] }; case "get_boleto": - return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("GET", `/v5/transactions/billpayments/bankslip/${args?.transactionId}`), null, 2) }] }; + // Query a boleto issued via the bill-issuance product by its id. + return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("GET", `/billissuance/v1/bill/${args?.transactionId}`), null, 2) }] }; case "create_transfer": return { content: [{ type: "text", text: JSON.stringify(await celcoinRequest("POST", "/v5/transactions/transfer", args), null, 2) }] }; case "get_balance": @@ -474,7 +510,7 @@ async function main() { if (!sid && isInitializeRequest(req.body)) { const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } }); t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); }; - const s = new Server({ name: "mcp-celcoin", version: "0.2.1" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t); + const s = new Server({ name: "mcp-celcoin", version: "0.2.3" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t); await t.handleRequest(req, res, req.body); return; } res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null }); diff --git a/packages/payments/celcoin/tsconfig.json b/packages/payments/celcoin/tsconfig.json index 40c56c6e..d6518af0 100644 --- a/packages/payments/celcoin/tsconfig.json +++ b/packages/payments/celcoin/tsconfig.json @@ -10,5 +10,6 @@ "esModuleInterop": true, "declaration": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/__tests__"] }