kanithan-xplit-mcp 1.1.1

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.
Files changed (3) hide show
  1. package/README.md +67 -0
  2. package/bin/index.mjs +220 -0
  3. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Kanithan Xplit MCP Server
2
+
3
+ The official Model Context Protocol (MCP) server for the Kanithan Xplit Expense, Habit & Trip Tracker.
4
+
5
+ This server allows AI agents (like Claude Desktop, Cline, and Cursor) to securely interact with your personal Kanithan database to log expenses and habits directly via conversational prompts.
6
+
7
+ ## Setup Instructions
8
+
9
+ ### 1. Generate an API Token
10
+ First, log into your Kanithan account and navigate to **Settings > API Access**. Generate a new Personal Access Token (PAT) with the necessary scopes (e.g., `expenses:write`, `habits:read`).
11
+
12
+ Copy this token—it will start with `knth_`.
13
+
14
+ ### 2. Configure Your AI Agent
15
+
16
+ You do not need to clone this repository. Simply add the following configuration to your MCP client (like Claude Desktop's `claude_desktop_config.json` or Cline's `cline_mcp_settings.json`):
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "kanithan": {
22
+ "command": "npx",
23
+ "args": ["-y", "kanithan-xplit-mcp"],
24
+ "env": {
25
+ "KANITHAN_XPLIT_API_TOKEN": "knth_YOUR_GENERATED_TOKEN_HERE"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ By default, the server connects to the production Kanithan API. If you are a developer testing locally or on the dev environment, you can override the API URL by adding the `KANITHAN_API_URL` environment variable:
33
+
34
+ ```json
35
+ "env": {
36
+ "KANITHAN_XPLIT_API_TOKEN": "knth_YOUR_GENERATED_TOKEN_HERE",
37
+ "KANITHAN_API_URL": "https://family-expense-tracker-fe21f.web.app/api/v1"
38
+ }
39
+ ```
40
+
41
+ ## Supported Tools
42
+
43
+ ### Habit Tools
44
+ - `list_habits` — Discover your configured habits and their internal IDs.
45
+ - `log_habit` — Log a specific value or boolean status to a habit on a given date.
46
+ - `get_habit_summary` — **NEW** Get a monthly breakdown for a habit: completed days, total value, and per-day detail.
47
+
48
+ ### Expense Tools
49
+ - `log_expense` — Log a daily expense into your family dashboard.
50
+ - `delete_expense` — **NEW** Permanently delete an expense by ID (owner only).
51
+ - `edit_expense` — **NEW** Update amount, category, date, or note on an existing expense (owner only).
52
+
53
+ ### Trip Tools
54
+ - `list_trips` — List all trips you are a member of (use this to get `tripId`).
55
+ - `create_trip` — Create a new trip with a name, description, and dates.
56
+ - `add_trip_expense` — Add an expense to a specific trip, with payer and optional splits.
57
+ - `get_trip_settlement` — Get the full settlement report: who paid, net balances, and who owes whom.
58
+
59
+ ## API Documentation
60
+
61
+ Interactive Swagger UI is available after app deployment at:
62
+
63
+ ```
64
+ https://app.kanithan.in/api/v1/swagger.html
65
+ ```
66
+
67
+ The raw OpenAPI 3.0 spec is at `/api/v1/openapi.json`.
package/bin/index.mjs ADDED
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+
10
+ const API_URL = process.env.KANITHAN_API_URL || "https://kanithan-prod.web.app/api/v1";
11
+ const API_TOKEN = process.env.KANITHAN_XPLIT_API_TOKEN;
12
+ const API_BASE = "https://api-uzsx2irlva-el.a.run.app";
13
+
14
+ if (!API_TOKEN) {
15
+ console.error("Error: KANITHAN_XPLIT_API_TOKEN environment variable is required.");
16
+ process.exit(1);
17
+ }
18
+
19
+ const server = new Server(
20
+ {
21
+ name: "kanithan-xplit-mcp",
22
+ version: "1.0.0",
23
+ },
24
+ {
25
+ capabilities: {
26
+ tools: {},
27
+ },
28
+ }
29
+ );
30
+
31
+ // Register Tools
32
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
33
+ return {
34
+ tools: [
35
+ {
36
+ name: "list_groups",
37
+ description: "List all expense groups you are a member of. Use this to find the correct groupId before calling add_group_expense or get_group_settlement.",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {}
41
+ }
42
+ },
43
+ {
44
+ name: "create_group",
45
+ description: "Create a new expense group",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ name: { type: "string", description: "Name of the group" },
50
+ description: { type: "string", description: "Optional description" },
51
+ startDate: { type: "string", description: "YYYY-MM-DD" },
52
+ endDate: { type: "string", description: "YYYY-MM-DD (optional)" }
53
+ },
54
+ required: ["name", "startDate"]
55
+ }
56
+ },
57
+ {
58
+ name: "add_group_expense",
59
+ description: "Add an expense to a specific group. Call list_groups first to get the groupId.",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {
63
+ groupId: { type: "string", description: "The ID of the group" },
64
+ amount: { type: "number" },
65
+ category: { type: "string" },
66
+ description: { type: "string" },
67
+ date: { type: "string", description: "YYYY-MM-DD" },
68
+ paidBy: { type: "string", description: "Display name of person who paid" },
69
+ splits: {
70
+ type: "array",
71
+ description: "Optional: how to split the expense among members",
72
+ items: {
73
+ type: "object",
74
+ properties: {
75
+ userName: { type: "string" },
76
+ amount: { type: "number" }
77
+ }
78
+ }
79
+ }
80
+ },
81
+ required: ["groupId", "amount", "category", "date", "paidBy"]
82
+ }
83
+ },
84
+ {
85
+ name: "get_group_settlement",
86
+ description: "Get the full settlement report for a group: who paid what, net balances, and who needs to pay whom to settle up.",
87
+ inputSchema: {
88
+ type: "object",
89
+ properties: {
90
+ groupId: { type: "string", description: "The ID of the group" }
91
+ },
92
+ required: ["groupId"]
93
+ }
94
+ },
95
+ {
96
+ name: "delete_expense",
97
+ description: "Permanently delete an expense by its ID. Only the owner of the expense can delete it.",
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ expenseId: { type: "string", description: "The ID of the expense to delete" }
102
+ },
103
+ required: ["expenseId"]
104
+ }
105
+ },
106
+ {
107
+ name: "edit_expense",
108
+ description: "Update one or more fields of an existing expense (amount, category, date, note). Provide the expenseId plus any fields you want to change. Only the owner of the expense can edit it.",
109
+ inputSchema: {
110
+ type: "object",
111
+ properties: {
112
+ expenseId: { type: "string", description: "The ID of the expense to edit" },
113
+ amount: { type: "number", description: "New amount" },
114
+ category: { type: "string", description: "New category" },
115
+ date: { type: "string", description: "New date in YYYY-MM-DD format" },
116
+ note: { type: "string", description: "New note or description" }
117
+ },
118
+ required: ["expenseId"]
119
+ }
120
+ }
121
+ ]
122
+ };
123
+ });
124
+
125
+ // Handle Tool Execution
126
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
127
+ const { name, arguments: args } = request.params;
128
+
129
+ const headers = {
130
+ "Content-Type": "application/json",
131
+ "Authorization": `Bearer ${API_TOKEN}`
132
+ };
133
+
134
+ try {
135
+ let response;
136
+
137
+ switch (name) {
138
+ case "list_groups":
139
+ response = await fetch(`${API_URL}/trips`, { headers });
140
+ break;
141
+
142
+ case "create_group":
143
+ response = await fetch(`${API_URL}/trips`, {
144
+ method: "POST",
145
+ headers,
146
+ body: JSON.stringify(args)
147
+ });
148
+ break;
149
+
150
+ case "add_group_expense": {
151
+ const { groupId, ...expenseArgs } = args;
152
+ response = await fetch(`${API_URL}/trips/${groupId}/expenses`, {
153
+ method: "POST",
154
+ headers,
155
+ body: JSON.stringify(expenseArgs)
156
+ });
157
+ break;
158
+ }
159
+
160
+ case "get_group_settlement": {
161
+ const { groupId: tId } = args;
162
+ response = await fetch(`${API_URL}/trips/${tId}/settlement`, { headers });
163
+ break;
164
+ }
165
+
166
+ case "delete_expense": {
167
+ const { expenseId } = args;
168
+ response = await fetch(`${API_URL}/expenses/${expenseId}`, {
169
+ method: "DELETE",
170
+ headers
171
+ });
172
+ break;
173
+ }
174
+
175
+ case "edit_expense": {
176
+ const { expenseId: eId, ...updateFields } = args;
177
+ response = await fetch(`${API_URL}/expenses/${eId}`, {
178
+ method: "PATCH",
179
+ headers,
180
+ body: JSON.stringify(updateFields)
181
+ });
182
+ break;
183
+ }
184
+
185
+ default:
186
+ throw new Error(`Unknown tool: ${name}`);
187
+
188
+ }
189
+
190
+ const result = await response.json();
191
+
192
+ if (!response.ok) {
193
+ throw new Error(`API Error: ${JSON.stringify(result)}`);
194
+ }
195
+
196
+ return {
197
+ content: [
198
+ {
199
+ type: "text",
200
+ text: JSON.stringify(result, null, 2)
201
+ }
202
+ ]
203
+ };
204
+ } catch (error) {
205
+ return {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: `Error executing tool ${name}: ${error.message}`
210
+ }
211
+ ],
212
+ isError: true
213
+ };
214
+ }
215
+ });
216
+
217
+ // Start Server
218
+ const transport = new StdioServerTransport();
219
+ await server.connect(transport);
220
+ console.error("Kanithan MCP Server running on stdio");
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "kanithan-xplit-mcp",
3
+ "version": "1.1.1",
4
+ "description": "MCP Server for Xplit.",
5
+ "main": "bin/index.mjs",
6
+ "type": "module",
7
+ "bin": {
8
+ "kanithan-xplit-mcp": "./bin/index.mjs"
9
+ },
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "kanithan",
16
+ "expense-tracker",
17
+ "ai",
18
+ "claude",
19
+ "cline"
20
+ ],
21
+ "author": "Joseph Vijayakumar",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.6.0"
25
+ }
26
+ }