@openfinance-sh/mcp 0.1.0
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/build/index.d.ts +1 -0
- package/build/index.js +174 -0
- package/manifest.json +54 -0
- package/openfinance.mcpb +0 -0
- package/package.json +31 -0
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/build/index.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
import { createServer } from "http";
|
|
8
|
+
|
|
9
|
+
// src/client.ts
|
|
10
|
+
var OpenFinanceClient = class {
|
|
11
|
+
baseUrl;
|
|
12
|
+
apiKey;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
15
|
+
this.apiKey = config.apiKey;
|
|
16
|
+
}
|
|
17
|
+
async request(path, params) {
|
|
18
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
19
|
+
if (params) {
|
|
20
|
+
for (const [key, value] of Object.entries(params)) {
|
|
21
|
+
if (value !== void 0 && value !== "") {
|
|
22
|
+
url.searchParams.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const res = await fetch(url.toString(), {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
29
|
+
"Content-Type": "application/json"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const body = await res.text();
|
|
34
|
+
throw new Error(`API error ${res.status}: ${body}`);
|
|
35
|
+
}
|
|
36
|
+
return res.json();
|
|
37
|
+
}
|
|
38
|
+
async getAccounts() {
|
|
39
|
+
return this.request("/api/accounts");
|
|
40
|
+
}
|
|
41
|
+
async getTransactions(filter) {
|
|
42
|
+
const params = {};
|
|
43
|
+
if (filter?.startDate) params.startDate = filter.startDate;
|
|
44
|
+
if (filter?.endDate) params.endDate = filter.endDate;
|
|
45
|
+
if (filter?.searchText) params.search = filter.searchText;
|
|
46
|
+
if (filter?.merchants?.length)
|
|
47
|
+
params.merchants = filter.merchants.join(",");
|
|
48
|
+
if (filter?.accountId !== void 0)
|
|
49
|
+
params.accountId = String(filter.accountId);
|
|
50
|
+
if (filter?.limit !== void 0) params.limit = String(filter.limit);
|
|
51
|
+
if (filter?.cursor) params.cursor = filter.cursor;
|
|
52
|
+
if (filter?.pending !== void 0) params.pending = String(filter.pending);
|
|
53
|
+
if (filter?.status?.length) params.status = filter.status.join(",");
|
|
54
|
+
return this.request("/api/transactions", params);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/tools/accounts.ts
|
|
59
|
+
function registerAccountTools(server, client) {
|
|
60
|
+
server.tool(
|
|
61
|
+
"get_accounts",
|
|
62
|
+
"Get all connected financial accounts with balances and institution info",
|
|
63
|
+
{},
|
|
64
|
+
async () => {
|
|
65
|
+
const { accounts } = await client.getAccounts();
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: JSON.stringify(accounts, null, 2)
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/tools/transactions.ts
|
|
79
|
+
import { z } from "zod";
|
|
80
|
+
function registerTransactionTools(server, client) {
|
|
81
|
+
server.tool(
|
|
82
|
+
"get_transactions",
|
|
83
|
+
"Search and filter financial transactions. Returns transactions sorted by date (newest first) by default.",
|
|
84
|
+
{
|
|
85
|
+
startDate: z.string().describe("Start date filter (YYYY-MM-DD)").optional(),
|
|
86
|
+
endDate: z.string().describe("End date filter (YYYY-MM-DD)").optional(),
|
|
87
|
+
search: z.string().describe("Search by transaction name or merchant").optional(),
|
|
88
|
+
merchants: z.array(z.string()).describe("Filter by exact merchant names").optional(),
|
|
89
|
+
accountId: z.number().int().describe("Filter by account ID").optional(),
|
|
90
|
+
limit: z.number().int().positive().max(500).describe("Max results (default 100, max 500)").optional(),
|
|
91
|
+
cursor: z.string().describe("Cursor for pagination").optional(),
|
|
92
|
+
pending: z.boolean().describe("Filter pending transactions").optional(),
|
|
93
|
+
status: z.array(z.enum(["active", "hidden", "deleted"])).describe("Filter by status").optional()
|
|
94
|
+
},
|
|
95
|
+
async (params) => {
|
|
96
|
+
const result = await client.getTransactions({
|
|
97
|
+
startDate: params.startDate,
|
|
98
|
+
endDate: params.endDate,
|
|
99
|
+
searchText: params.search,
|
|
100
|
+
merchants: params.merchants,
|
|
101
|
+
accountId: params.accountId,
|
|
102
|
+
limit: params.limit,
|
|
103
|
+
cursor: params.cursor,
|
|
104
|
+
pending: params.pending,
|
|
105
|
+
status: params.status
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify(result.transactions, null, 2)
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/index.ts
|
|
120
|
+
function parseArgs() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
let transport = "stdio";
|
|
123
|
+
let port = 8080;
|
|
124
|
+
for (let i = 0; i < args.length; i++) {
|
|
125
|
+
if (args[i] === "--transport" && args[i + 1]) {
|
|
126
|
+
transport = args[i + 1];
|
|
127
|
+
i++;
|
|
128
|
+
} else if (args[i] === "--port" && args[i + 1]) {
|
|
129
|
+
port = parseInt(args[i + 1], 10);
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { transport, port };
|
|
134
|
+
}
|
|
135
|
+
async function main() {
|
|
136
|
+
const apiKey = process.env.OPENFINANCE_API_KEY;
|
|
137
|
+
const baseUrl = process.env.OPENFINANCE_URL || "http://localhost:3000";
|
|
138
|
+
if (!apiKey) {
|
|
139
|
+
console.error(
|
|
140
|
+
"Error: OPENFINANCE_API_KEY environment variable is required"
|
|
141
|
+
);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const { transport, port } = parseArgs();
|
|
145
|
+
const client = new OpenFinanceClient({ baseUrl, apiKey });
|
|
146
|
+
const server = new McpServer({
|
|
147
|
+
name: "openfinance",
|
|
148
|
+
version: "0.1.0"
|
|
149
|
+
});
|
|
150
|
+
registerAccountTools(server, client);
|
|
151
|
+
registerTransactionTools(server, client);
|
|
152
|
+
if (transport === "stdio") {
|
|
153
|
+
const stdioTransport = new StdioServerTransport();
|
|
154
|
+
await server.connect(stdioTransport);
|
|
155
|
+
console.error("OpenFinance MCP server running on stdio");
|
|
156
|
+
} else {
|
|
157
|
+
const httpTransport = new StreamableHTTPServerTransport({
|
|
158
|
+
sessionIdGenerator: void 0
|
|
159
|
+
});
|
|
160
|
+
await server.connect(httpTransport);
|
|
161
|
+
const httpServer = createServer(async (req, res) => {
|
|
162
|
+
await httpTransport.handleRequest(req, res);
|
|
163
|
+
});
|
|
164
|
+
httpServer.listen(port, () => {
|
|
165
|
+
console.error(
|
|
166
|
+
`OpenFinance MCP server running on http://localhost:${port}`
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
main().catch((err) => {
|
|
172
|
+
console.error("Fatal error:", err);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
});
|
package/manifest.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.3",
|
|
3
|
+
"name": "openfinance",
|
|
4
|
+
"display_name": "OpenFinance",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Connect your financial accounts to view balances, search transactions, and get spending insights.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "OpenFinance"
|
|
9
|
+
},
|
|
10
|
+
"server": {
|
|
11
|
+
"type": "node",
|
|
12
|
+
"entry_point": "build/index.js",
|
|
13
|
+
"mcp_config": {
|
|
14
|
+
"command": "node",
|
|
15
|
+
"args": ["${__dirname}/build/index.js"],
|
|
16
|
+
"env": {
|
|
17
|
+
"OPENFINANCE_API_KEY": "${user_config.api_key}",
|
|
18
|
+
"OPENFINANCE_URL": "${user_config.server_url}"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"user_config": {
|
|
23
|
+
"api_key": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"title": "API Key",
|
|
26
|
+
"description": "Your OpenFinance API key (starts with sk-)",
|
|
27
|
+
"sensitive": true,
|
|
28
|
+
"required": true
|
|
29
|
+
},
|
|
30
|
+
"server_url": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"title": "Server URL",
|
|
33
|
+
"description": "URL of your OpenFinance server",
|
|
34
|
+
"required": true,
|
|
35
|
+
"default": "https://api.openfinance.sh"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"tools": [
|
|
39
|
+
{
|
|
40
|
+
"name": "get_accounts",
|
|
41
|
+
"description": "Get all connected financial accounts with balances and institution info"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "get_transactions",
|
|
45
|
+
"description": "Search and filter financial transactions. Returns transactions sorted by date (newest first) by default. For spending analysis, category breakdowns, or questions like 'how much do I spend on X', fetch all transactions (high limit, no search filter) and categorize them client-side by analyzing merchant/transaction names, since transactions do not have category labels."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "query_transactions",
|
|
49
|
+
"description": "Run a SQL query against the user's transactions for aggregation and analysis. Write SELECT queries against a `txns` CTE with columns: id, name, amount, date, merchant_name, etc. Runs read-only with 5s timeout and 1000 row limit."
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"keywords": ["finance", "banking", "transactions", "accounts"],
|
|
53
|
+
"license": "MIT"
|
|
54
|
+
}
|
package/openfinance.mcpb
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openfinance-sh/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"openfinance-mcp": "./build/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "tsx src/index.ts",
|
|
10
|
+
"build": "tsup src/index.ts --format esm --dts --clean --outDir build",
|
|
11
|
+
"start": "node build/index.js",
|
|
12
|
+
"bundle": "pnpm build && mcpb pack . openfinance.mcpb"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
16
|
+
"@openfinance/shared": "workspace:*",
|
|
17
|
+
"zod": "^3.24.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"build",
|
|
21
|
+
"manifest.json",
|
|
22
|
+
"openfinance.mcpb"
|
|
23
|
+
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"@anthropic-ai/mcpb": "^2.1.2",
|
|
27
|
+
"tsup": "^8.4.0",
|
|
28
|
+
"tsx": "^4.19.0",
|
|
29
|
+
"typescript": "^5.7.0"
|
|
30
|
+
}
|
|
31
|
+
}
|