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.
- package/README.md +67 -0
- package/bin/index.mjs +220 -0
- 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
|
+
}
|