feat: initial public release
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
||||
import { createApprovalMcpServer } from "../dist/src/server.js";
|
||||
import { XftOpenApiClient } from "../dist/src/xft-open-api.js";
|
||||
|
||||
const [appId, secretFile, enterpriseId, dingTalkUserId] = process.argv.slice(2);
|
||||
if (!appId || !secretFile || !enterpriseId || !dingTalkUserId) {
|
||||
throw new Error("usage: xft-mcp-readonly-matrix.mjs <appId> <secretFile> <enterpriseId> <dingTalkUserId>");
|
||||
}
|
||||
const lines = readFileSync(secretFile, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
||||
const config = {
|
||||
appMode: "gateway",
|
||||
clientId: "readonly-mcp", clientSecret: "readonly-mcp",
|
||||
apiBaseUrl: "https://api.dingtalk.com", oapiBaseUrl: "https://oapi.dingtalk.com",
|
||||
publicBaseUrl: "https://readonly.invalid", port: 3000, requestTimeoutMs: 15_000,
|
||||
officialLookbackDays: 120, officialScanConcurrency: 2, officialClientOnlyProcessCodes: [],
|
||||
batchLimit: 20, approvalRefSecret: "readonly-matrix-reference-secret",
|
||||
xftOpenApi: {
|
||||
appId, authoritySecret: lines.length >= 2 ? lines[1] : lines[0], enterpriseId,
|
||||
apiBaseUrl: "https://api.cmbchina.com", encrypted: true, dingTalkAppCode: "DDingTalk",
|
||||
},
|
||||
};
|
||||
const xft = new XftOpenApiClient(config);
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
const server = createApprovalMcpServer(
|
||||
config,
|
||||
{ async listMyTodos() { throw new Error("DingTalk source must not be called"); } },
|
||||
xft,
|
||||
undefined,
|
||||
undefined,
|
||||
{ userId: dingTalkUserId },
|
||||
);
|
||||
const client = new Client({ name: "xft-readonly-matrix", version: "1.0.0" });
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
try {
|
||||
const listed = result(await client.callTool({ name: "approval_list_todos", arguments: { source: "xft", query: "", limit: 100 } }));
|
||||
const todos = Array.isArray(listed.items) ? listed.items : [];
|
||||
const selected = distinctBy(todos, (item) => `${item.title ?? ""}|${item.amount?.value ?? ""}`).slice(0, 8);
|
||||
const flows = [];
|
||||
for (const todo of selected) {
|
||||
try {
|
||||
const summary = result(await client.callTool({ name: "approval_get_detail", arguments: { approvalId: todo.approvalId, view: "summary" } }));
|
||||
const expense = result(await client.callTool({ name: "approval_get_detail", arguments: { approvalId: todo.approvalId, view: "expense" } }));
|
||||
const actions = result(await client.callTool({ name: "approval_get_actions", arguments: { approvalId: todo.approvalId } }));
|
||||
flows.push({
|
||||
businessNo: todo.businessNo, title: todo.title, applicant: todo.applicant, amount: todo.amount, status: "ok",
|
||||
summaryFields: Array.isArray(summary.detail?.formFields) ? summary.detail.formFields.length : 0,
|
||||
expenseItems: Array.isArray(expense.detail?.expenseItems) ? expense.detail.expenseItems.length : 0,
|
||||
actions: Array.isArray(actions.actions) ? actions.actions.map((action) => ({ action: action.action, allowed: action.allowed })) : [],
|
||||
rawOmitted: summary.diagnostics?.rawOmitted === true,
|
||||
});
|
||||
} catch (error) {
|
||||
flows.push({ businessNo: todo.businessNo, title: todo.title, status: "failed", error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify({ totalTodos: todos.length, testedFlows: flows.length, sourceStatus: listed.sourceStatus, flows }, null, 2));
|
||||
} finally {
|
||||
await client.close();
|
||||
await server.close();
|
||||
}
|
||||
|
||||
function result(call) {
|
||||
const value = call?.structuredContent?.result;
|
||||
if (!value || typeof value !== "object") throw new Error("MCP tool did not return structuredContent.result");
|
||||
return value;
|
||||
}
|
||||
|
||||
function distinctBy(items, key) {
|
||||
const seen = new Set();
|
||||
return items.filter((item) => { const value = key(item); if (seen.has(value)) return false; seen.add(value); return true; });
|
||||
}
|
||||
Reference in New Issue
Block a user