vtiger-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ViralP17
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # vtiger-mcp
2
+
3
+ **MCP server + CLI for [vtiger CRM](https://www.vtiger.com/).** Connect any
4
+ AI agent (Claude Desktop, Claude Code, Cursor, …) to your vtiger CRM to
5
+ **log timesheets** and **create / update records in any module** β€” in
6
+ plain English.
7
+
8
+ It talks to vtiger's built-in REST API (`/webservice.php`, available in
9
+ vtiger 6.x / 7.x). It adds **no** access rules of its own: every call runs
10
+ **as the authenticated user**, so vtiger enforces that user's role,
11
+ profile, and row-level sharing automatically. Each person uses their own
12
+ **Webservice Access Key**, so they only see and change what their account
13
+ already allows.
14
+
15
+ > New to the terminal? See **[SETUP.md](SETUP.md)** for a click-by-click,
16
+ > non-developer guide.
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - πŸ” **Per-user auth** via each user's Webservice Access Key β€” row-level
23
+ access preserved, nothing centralized.
24
+ - 🧩 **Any module**: `create`, `update`, `query`, `describe`, `retrieve`,
25
+ `delete` β€” subject to the user's permissions.
26
+ - ⏱️ **Timesheet helpers** for the time-tracking module (create/list/update
27
+ entries, resolve tasks by name).
28
+ - πŸ–₯️ Ships as both an **MCP server** (for AI agents) and a **CLI**.
29
+ - πŸ“¦ Zero changes to your vtiger install. Node 18+, no native deps.
30
+
31
+ ## Requirements
32
+
33
+ - **Node.js 18+**
34
+ - A vtiger CRM (6.x / 7.x) whose `/webservice.php` endpoint is reachable
35
+ - Your **Webservice Access Key** β€” in the CRM: *My Preferences β†’
36
+ "Webservice Access Key"* (a 16-char string; **not** your login password)
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ # Global (gives you the `vtiger` CLI and the `vtiger-mcp` server binary)
42
+ npm install -g vtiger-mcp
43
+
44
+ # …or run without installing
45
+ npx vtiger-mcp # starts the MCP server (stdio)
46
+ ```
47
+
48
+ Or from source:
49
+
50
+ ```bash
51
+ git clone https://github.com/ViralP17/vtiger-mcp.git
52
+ cd vtiger-mcp
53
+ npm install
54
+ npm run build
55
+ ```
56
+
57
+ ## Configure
58
+
59
+ Set three environment variables (via your MCP client config, or a `.env`
60
+ file for the CLI β€” see [.env.example](.env.example)):
61
+
62
+ | Variable | Example | Notes |
63
+ |---|---|---|
64
+ | `VTIGER_URL` | `https://yourcrmdomain.com` | Base URL, no `/webservice.php` |
65
+ | `VTIGER_USERNAME` | `your.username` | Your vtiger login |
66
+ | `VTIGER_ACCESS_KEY` | `xxxxxxxxxxxxxxxx` | Webservice Access Key |
67
+ | `VTIGER_ALLOW_DELETE` | `false` | Set `true` to allow `delete` |
68
+
69
+ ## Use as an MCP server
70
+
71
+ Point your AI client at `vtiger-mcp` and pass your credentials in `env`.
72
+
73
+ **Via npx (after `npm install -g vtiger-mcp` or letting npx fetch it):**
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "vtiger": {
79
+ "command": "npx",
80
+ "args": ["-y", "vtiger-mcp"],
81
+ "env": {
82
+ "VTIGER_URL": "https://yourcrmdomain.com",
83
+ "VTIGER_USERNAME": "your.username",
84
+ "VTIGER_ACCESS_KEY": "your-access-key",
85
+ "VTIGER_ALLOW_DELETE": "false"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ **From a source checkout** (use `node` + the built server path):
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "vtiger": {
98
+ "command": "node",
99
+ "args": ["/path/to/vtiger-mcp/dist/mcp/server.js"],
100
+ "env": { "VTIGER_URL": "https://yourcrmdomain.com", "VTIGER_USERNAME": "your.username", "VTIGER_ACCESS_KEY": "your-access-key" }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ - **Claude Desktop** config: `%APPDATA%\Claude\claude_desktop_config.json`
107
+ (Windows) / `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
108
+ - **Cursor / Claude Code**: a `.mcp.json` with the same `mcpServers` shape
109
+
110
+ Then just ask, e.g. *"Log 3 hours to the Website Redesign task today as
111
+ regular hours"* or *"Show my timesheets this week."*
112
+
113
+ ## Use as a CLI
114
+
115
+ ```bash
116
+ export VTIGER_URL=https://yourcrmdomain.com
117
+ export VTIGER_USERNAME=your.username
118
+ export VTIGER_ACCESS_KEY=your-access-key # or put these in a .env file
119
+
120
+ vtiger whoami
121
+ vtiger modules
122
+ vtiger describe TimeControl
123
+ vtiger query "SELECT tc_code, date_start, totaltime FROM TimeControl LIMIT 5;"
124
+ vtiger log-timesheet --task "Website Redesign" --start 10:00 --end 13:00 --type "Regular Hours" --activity "API work"
125
+ vtiger my-timesheets --from 2026-07-01 --to 2026-07-31
126
+ vtiger create ProjectTask --set projecttaskname="Write docs" --set projectid=<id>
127
+ vtiger update 17x1234 --set description="Revised notes"
128
+ ```
129
+
130
+ ## Tools exposed to the AI agent
131
+
132
+ **Any module (scoped to your access):**
133
+
134
+ | Tool | What it does |
135
+ |---|---|
136
+ | `list_modules` | Modules you can access |
137
+ | `describe_module` | Live fields of a module (names, mandatory, picklists, references) |
138
+ | `query` | Read-only VTQL `SELECT` |
139
+ | `retrieve_record` | Fetch one record by id |
140
+ | `create_record` | Create a record in any writable module |
141
+ | `update_record` | Partial update of a record |
142
+ | `delete_record` | Delete (only if `VTIGER_ALLOW_DELETE=true`) |
143
+
144
+ **Timesheet helpers (the time-tracking module):**
145
+
146
+ | Tool | What it does |
147
+ |---|---|
148
+ | `whoami` | Your vtiger user id / profile |
149
+ | `list_my_projects` | Projects you can access |
150
+ | `list_my_tasks` | Project tasks (for the `related_to` link) |
151
+ | `log_timesheet` | Add a daily entry (resolves task, defaults date/owner, computes total) |
152
+ | `list_my_timesheets` | Your entries in a date range |
153
+ | `update_timesheet` | Edit an existing entry |
154
+
155
+ ## Notes & tips
156
+
157
+ - **Field names vary per install.** vtiger schemas are customizable, so
158
+ always `describe_module` (or `vtiger describe <Module>`) first to learn
159
+ the exact field names before creating/updating.
160
+ - **Ids** are vtiger "webservice ids" like `17x1234`
161
+ (`<moduleId>x<recordId>`). Reference fields (owner, `related_to`,
162
+ `projectid`) expect these ids; the timesheet helpers resolve task names
163
+ to ids for you.
164
+ - **The timesheet module name** in this tool defaults to `TimeControl`.
165
+ If your install uses a different module for time tracking, use the
166
+ generic `create_record` / `query` tools, or adjust the constants in
167
+ `src/core/timesheet.ts`.
168
+ - **Deletes are off by default.** Set `VTIGER_ALLOW_DELETE=true` only if you
169
+ want the agent/CLI to delete records (and your account has that right).
170
+ - **Security.** Your access key is a secret β€” keep it in your local `.env`
171
+ or client config only. Use HTTPS. Nothing is centralized; each user runs
172
+ their own copy with their own key.
173
+
174
+ ## License
175
+
176
+ [MIT](LICENSE) Β© ViralP17
package/SETUP.md ADDED
@@ -0,0 +1,182 @@
1
+ # Setup Guide β€” for everyone (no coding needed)
2
+
3
+ This lets you **log your daily timesheet (Time Control) and add/update
4
+ records in the vtiger portal just by chatting** with an AI assistant
5
+ (Claude Desktop). After a one-time setup, you never touch any code or
6
+ terminal β€” you simply type things like:
7
+
8
+ > "Log 4 hours to the Website Redesign task today as regular hours."
9
+ > "Show my timesheets for this week."
10
+ > "Add a 2 hour entry to task X yesterday: fixed the login bug."
11
+
12
+ The assistant does it using **your own** vtiger account, so you can only
13
+ see and change what you're already allowed to in the portal.
14
+
15
+ ---
16
+
17
+ ## What you need (once)
18
+
19
+ 1. **Node.js** β€” free. Install the **LTS** version from <https://nodejs.org/>
20
+ (just click through the installer with defaults).
21
+ 2. **Claude Desktop** β€” the app you'll chat in. Download from
22
+ <https://claude.ai/download> and sign in.
23
+ 3. **This `vtiger-mcp` folder** on your computer (ask your team lead for
24
+ it, or clone the repo).
25
+ 4. **Your Webservice Access Key** (see next section).
26
+
27
+ ### How to find your Webservice Access Key
28
+
29
+ This is **not** your login password.
30
+
31
+ 1. Log in to your vtiger CRM (e.g. https://yourcrmdomain.com).
32
+ 2. Click your **name** at the top-right β†’ **My Preferences**.
33
+ 3. Scroll to the field **"Webservice Access Key"** (a 16-character code).
34
+ 4. Copy it. You'll paste it once during setup.
35
+
36
+ > If that field is empty, ask an admin to re-save your user record so a
37
+ > key gets generated.
38
+
39
+ ---
40
+
41
+ ## Track A β€” Easiest (Windows + Claude Desktop)
42
+
43
+ A helper script does everything for you.
44
+
45
+ 1. Open the `vtiger-mcp` folder in File Explorer.
46
+ 2. **Right-click `setup.ps1` β†’ "Run with PowerShell".**
47
+ - If Windows blocks it, open PowerShell and run:
48
+ `powershell -ExecutionPolicy Bypass -File setup.ps1`
49
+ 3. Answer the two prompts:
50
+ - **Portal URL** β€” just press Enter to accept the default.
51
+ - **Username** β€” your vtiger login (e.g. `jsmith`).
52
+ - **Access Key** β€” paste the key from My Preferences.
53
+ 4. When it says *"Config written"*, **fully quit Claude Desktop**
54
+ (right-click the tray icon β†’ Quit) and **open it again**.
55
+ 5. Start chatting β€” try: *"Show my timesheets for this week."*
56
+
57
+ That's it. You're done. πŸŽ‰
58
+
59
+ ---
60
+
61
+ ## Track B β€” Manual setup (Mac/Linux, or Cursor / Claude Code)
62
+
63
+ If you're not on Windows, or you use a different AI client, add the
64
+ config by hand.
65
+
66
+ 1. In a terminal, inside the `vtiger-mcp` folder, run once:
67
+ ```
68
+ npm install
69
+ npm run build
70
+ ```
71
+ 2. Note the **full path** to `dist/mcp/server.js` inside this folder.
72
+ 3. Open your client's config file and add the `vtiger` block below,
73
+ replacing the path and your details. A ready template is in
74
+ [`examples/claude_desktop_config.json`](examples/claude_desktop_config.json).
75
+
76
+ **Claude Desktop config location:**
77
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
78
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
79
+
80
+ **Cursor:** a `.mcp.json` in your project (same `mcpServers` shape).
81
+ **Claude Code:** run `claude mcp add` or edit `.mcp.json`.
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "vtiger": {
87
+ "command": "node",
88
+ "args": ["/full/path/to/vtiger-mcp/dist/mcp/server.js"],
89
+ "env": {
90
+ "VTIGER_URL": "https://yourcrmdomain.com",
91
+ "VTIGER_USERNAME": "your.username",
92
+ "VTIGER_ACCESS_KEY": "your-access-key",
93
+ "VTIGER_ALLOW_DELETE": "false"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+ 4. Fully restart the client.
100
+
101
+ ---
102
+
103
+ ## What you can ask for
104
+
105
+ Timesheets (the daily Time Control entries):
106
+ - *"Log a timesheet: task 'Website Redesign', 9:00 to 13:00 today, regular hours, note 'team meeting + planning'."*
107
+ - *"Add 3 hours to task X yesterday as extra hours."*
108
+ - *"What tasks can I log time against?"* (lists your project tasks)
109
+ - *"Show my timesheets between 1 and 13 July."*
110
+ - *"Change the note on my last entry to '…'."*
111
+
112
+ Any other module you have access to:
113
+ - *"Create a project task called 'Write docs' under project Y."*
114
+ - *"Find contacts whose name contains 'Patel'."* (read-only search)
115
+ - *"Update record 17x1234: set the status to In Progress."*
116
+
117
+ The assistant will ask for anything it needs (like which task you mean)
118
+ and will show you what it created.
119
+
120
+ ---
121
+
122
+ ## Already using other MCP servers?
123
+
124
+ Track A **overwrites** `claude_desktop_config.json` (it saves a
125
+ `.backup` first). If you already have other servers configured, open the
126
+ `.backup` file and copy their entries into the new file's `mcpServers`
127
+ object, next to `"vtiger"`. Each server is just one more key:
128
+
129
+ ```json
130
+ "mcpServers": {
131
+ "vtiger": { ... },
132
+ "some-other-server": { ... }
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Troubleshooting
139
+
140
+ - **"vtiger" tools don't appear in Claude Desktop** β†’ make sure you
141
+ *fully quit* and reopened it (not just closed the window). Check the
142
+ config file has no typos (valid JSON).
143
+ - **"Access key for the user is undefined" / login fails** β†’ the Access
144
+ Key is wrong or empty. Re-copy it from My Preferences.
145
+ - **403 Forbidden / can't connect** β†’ the portal's `webservice.php` must
146
+ be reachable (an admin one-time change). Contact your team lead.
147
+ - **"Permission … denied" when creating/deleting** β†’ your vtiger account
148
+ doesn't have that right. This is expected β€” the tool respects your
149
+ portal permissions.
150
+ - **Deleting is blocked** β†’ deletes are off by default for safety. An
151
+ admin can enable it by setting `VTIGER_ALLOW_DELETE` to `true` in the
152
+ config, if your account even has delete rights.
153
+
154
+ ---
155
+
156
+ ## Fastest path for developers (npm)
157
+
158
+ If you're comfortable editing a JSON config, you don't need this folder at
159
+ all β€” the package is on npm. Just point your client at it with `npx`:
160
+
161
+ ```json
162
+ {
163
+ "mcpServers": {
164
+ "vtiger": {
165
+ "command": "npx",
166
+ "args": ["-y", "vtiger-mcp"],
167
+ "env": {
168
+ "VTIGER_URL": "https://yourcrmdomain.com",
169
+ "VTIGER_USERNAME": "your.username",
170
+ "VTIGER_ACCESS_KEY": "your-access-key",
171
+ "VTIGER_ALLOW_DELETE": "false"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ `npx` fetches and runs `vtiger-mcp` automatically. For a CLI, run
179
+ `npm install -g vtiger-mcp` then use `vtiger …` (see the main README).
180
+
181
+ For non-developers on Windows, **Track A (`setup.ps1`)** remains the
182
+ simplest per-person path.
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { createClientFromEnv } from "../core/client.js";
5
+ import { TIMESHEET_MODULE, PROJECT_MODULE, findTasks, buildTimesheetElement, listMyTimesheets, } from "../core/timesheet.js";
6
+ /**
7
+ * Thin CLI over the shared vtiger core client. Useful for quick testing,
8
+ * scripting, and for developers who prefer the terminal. Reads the same
9
+ * env vars as the MCP server; also loads a local .env if present.
10
+ */
11
+ // --- minimal .env loader (no dependency) ---------------------------------
12
+ function loadEnv() {
13
+ const path = resolve(process.cwd(), ".env");
14
+ if (!existsSync(path))
15
+ return;
16
+ for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed || trimmed.startsWith("#"))
19
+ continue;
20
+ const eq = trimmed.indexOf("=");
21
+ if (eq === -1)
22
+ continue;
23
+ const key = trimmed.slice(0, eq).trim();
24
+ let value = trimmed.slice(eq + 1).trim();
25
+ if ((value.startsWith('"') && value.endsWith('"')) ||
26
+ (value.startsWith("'") && value.endsWith("'"))) {
27
+ value = value.slice(1, -1);
28
+ }
29
+ if (process.env[key] === undefined)
30
+ process.env[key] = value;
31
+ }
32
+ }
33
+ function parseArgs(argv) {
34
+ const positionals = [];
35
+ const flags = {};
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const token = argv[i];
38
+ if (token.startsWith("--")) {
39
+ const key = token.slice(2);
40
+ const next = argv[i + 1];
41
+ if (next !== undefined && !next.startsWith("--")) {
42
+ (flags[key] ??= []).push(next);
43
+ i++;
44
+ }
45
+ else {
46
+ (flags[key] ??= []).push("true");
47
+ }
48
+ }
49
+ else {
50
+ positionals.push(token);
51
+ }
52
+ }
53
+ return { positionals, flags };
54
+ }
55
+ function flag(args, name) {
56
+ return args.flags[name]?.[0];
57
+ }
58
+ /** Turn repeated `--set field=value` into an object. */
59
+ function collectSet(args) {
60
+ const out = {};
61
+ for (const pair of args.flags["set"] ?? []) {
62
+ const eq = pair.indexOf("=");
63
+ if (eq === -1)
64
+ throw new Error(`--set expects field=value, got "${pair}"`);
65
+ out[pair.slice(0, eq)] = pair.slice(eq + 1);
66
+ }
67
+ return out;
68
+ }
69
+ function print(data) {
70
+ process.stdout.write((typeof data === "string" ? data : JSON.stringify(data, null, 2)) + "\n");
71
+ }
72
+ const USAGE = `vtiger β€” CLI for vtiger CRM
73
+
74
+ Usage:
75
+ vtiger whoami
76
+ vtiger modules
77
+ vtiger describe <Module>
78
+ vtiger query "<VTQL SELECT>"
79
+ vtiger retrieve <id>
80
+ vtiger create <Module> --set field=value [--set ...]
81
+ vtiger update <id> --set field=value [--set ...]
82
+ vtiger delete <id> (needs VTIGER_ALLOW_DELETE=true)
83
+
84
+ vtiger projects
85
+ vtiger tasks [nameFragment]
86
+ vtiger log-timesheet [--date YYYY-MM-DD] [--task "<name or id>"]
87
+ [--start HH:MM] [--end HH:MM] [--total HH:MM]
88
+ [--activity "..."] [--type "Regular Hours"]
89
+ vtiger my-timesheets [--from YYYY-MM-DD] [--to YYYY-MM-DD]
90
+
91
+ Credentials come from env (VTIGER_URL / VTIGER_USERNAME / VTIGER_ACCESS_KEY)
92
+ or a local .env file.`;
93
+ async function main() {
94
+ loadEnv();
95
+ const argv = process.argv.slice(2);
96
+ const command = argv[0];
97
+ const args = parseArgs(argv.slice(1));
98
+ if (!command || command === "help" || command === "--help" || command === "-h") {
99
+ print(USAGE);
100
+ return;
101
+ }
102
+ const client = createClientFromEnv();
103
+ switch (command) {
104
+ case "whoami": {
105
+ await client.listTypes();
106
+ let user = null;
107
+ try {
108
+ const rows = await client.query(`SELECT id, user_name, first_name, last_name, email1 FROM Users WHERE id = '${client.userId}';`);
109
+ user = rows[0] ?? null;
110
+ }
111
+ catch {
112
+ // Non-admin users cannot query the Users module; userId alone is enough.
113
+ }
114
+ print({ userId: client.userId, user });
115
+ break;
116
+ }
117
+ case "modules":
118
+ print(await client.listTypes());
119
+ break;
120
+ case "describe": {
121
+ const module = args.positionals[0];
122
+ if (!module)
123
+ throw new Error("Usage: vtiger describe <Module>");
124
+ print(await client.describe(module));
125
+ break;
126
+ }
127
+ case "query": {
128
+ const vtql = args.positionals[0];
129
+ if (!vtql)
130
+ throw new Error('Usage: vtiger query "<VTQL SELECT>"');
131
+ print(await client.query(vtql));
132
+ break;
133
+ }
134
+ case "retrieve": {
135
+ const id = args.positionals[0];
136
+ if (!id)
137
+ throw new Error("Usage: vtiger retrieve <id>");
138
+ print(await client.retrieve(id));
139
+ break;
140
+ }
141
+ case "create": {
142
+ const module = args.positionals[0];
143
+ if (!module)
144
+ throw new Error("Usage: vtiger create <Module> --set field=value");
145
+ print(await client.create(module, collectSet(args)));
146
+ break;
147
+ }
148
+ case "update": {
149
+ const id = args.positionals[0];
150
+ if (!id)
151
+ throw new Error("Usage: vtiger update <id> --set field=value");
152
+ print(await client.revise({ id, ...collectSet(args) }));
153
+ break;
154
+ }
155
+ case "delete": {
156
+ const id = args.positionals[0];
157
+ if (!id)
158
+ throw new Error("Usage: vtiger delete <id>");
159
+ print(await client.delete(id));
160
+ break;
161
+ }
162
+ case "projects":
163
+ print(await client.query(`SELECT id, projectname, projectstatus FROM ${PROJECT_MODULE} ORDER BY projectname LIMIT 100;`));
164
+ break;
165
+ case "tasks":
166
+ print(await findTasks(client, args.positionals[0] ?? ""));
167
+ break;
168
+ case "log-timesheet": {
169
+ await client.listTypes(); // ensure login -> userId
170
+ let taskId;
171
+ const task = flag(args, "task");
172
+ if (task) {
173
+ const matches = await findTasks(client, task);
174
+ if (matches.length === 0)
175
+ throw new Error(`No task matches "${task}".`);
176
+ if (matches.length > 1) {
177
+ print({ message: `Multiple tasks match "${task}"; re-run with the exact id.`, candidates: matches });
178
+ return;
179
+ }
180
+ taskId = matches[0].id;
181
+ }
182
+ const element = buildTimesheetElement({
183
+ date: flag(args, "date") ?? new Date().toISOString().slice(0, 10),
184
+ taskId,
185
+ startTime: flag(args, "start"),
186
+ endTime: flag(args, "end"),
187
+ totalTime: flag(args, "total"),
188
+ activity: flag(args, "activity"),
189
+ hoursType: flag(args, "type"),
190
+ assignedUserId: client.userId ?? undefined,
191
+ });
192
+ print(await client.create(TIMESHEET_MODULE, element));
193
+ break;
194
+ }
195
+ case "my-timesheets": {
196
+ await client.listTypes();
197
+ print(await listMyTimesheets(client, flag(args, "from"), flag(args, "to")));
198
+ break;
199
+ }
200
+ default:
201
+ print(`Unknown command: ${command}\n\n${USAGE}`);
202
+ process.exitCode = 1;
203
+ }
204
+ }
205
+ main().catch((err) => {
206
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
207
+ process.exit(1);
208
+ });
209
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ import { createHash } from "node:crypto";
2
+ /** Error thrown for any non-success vtiger response (carries the vtiger error code). */
3
+ export class VtigerError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(`[${code}] ${message}`);
7
+ this.name = "VtigerError";
8
+ this.code = code;
9
+ }
10
+ }
11
+ /** vtiger operations that read their input from $_GET (everything else is POST). */
12
+ const GET_OPERATIONS = new Set([
13
+ "getchallenge",
14
+ "retrieve",
15
+ "query",
16
+ "describe",
17
+ "listtypes",
18
+ "retrieve_related",
19
+ "getupdates",
20
+ ]);
21
+ function md5(input) {
22
+ return createHash("md5").update(input).digest("hex");
23
+ }
24
+ export class VtigerClient {
25
+ endpoint;
26
+ config;
27
+ session = null;
28
+ constructor(config) {
29
+ if (!config.url)
30
+ throw new Error("VTIGER_URL is required");
31
+ if (!config.username)
32
+ throw new Error("VTIGER_USERNAME is required");
33
+ if (!config.accessKey)
34
+ throw new Error("VTIGER_ACCESS_KEY is required");
35
+ this.config = config;
36
+ this.endpoint = config.url.replace(/\/+$/, "") + "/webservice.php";
37
+ }
38
+ /** The vtiger user id of the authenticated developer (e.g. "19x5"). Requires a prior login. */
39
+ get userId() {
40
+ return this.session?.userId ?? null;
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Low-level transport
44
+ // ---------------------------------------------------------------------------
45
+ /** Perform one HTTP call to webservice.php and unwrap the {success, result|error} envelope. */
46
+ async raw(operation, params) {
47
+ const method = GET_OPERATIONS.has(operation) ? "GET" : "POST";
48
+ const query = new URLSearchParams({ operation, ...params });
49
+ let response;
50
+ if (method === "GET") {
51
+ response = await fetch(`${this.endpoint}?${query.toString()}`, {
52
+ method: "GET",
53
+ headers: { Accept: "application/json" },
54
+ });
55
+ }
56
+ else {
57
+ response = await fetch(this.endpoint, {
58
+ method: "POST",
59
+ headers: {
60
+ "Content-Type": "application/x-www-form-urlencoded",
61
+ Accept: "application/json",
62
+ },
63
+ body: query.toString(),
64
+ });
65
+ }
66
+ const text = await response.text();
67
+ let payload;
68
+ try {
69
+ payload = JSON.parse(text);
70
+ }
71
+ catch {
72
+ throw new VtigerError("NON_JSON_RESPONSE", `Expected JSON from webservice.php but got (HTTP ${response.status}): ${text.slice(0, 300)}`);
73
+ }
74
+ if (payload && payload.success === true)
75
+ return payload.result;
76
+ const err = payload?.error ?? {};
77
+ throw new VtigerError(err.code ?? "UNKNOWN_ERROR", err.message ?? "Unknown error");
78
+ }
79
+ isSessionError(err) {
80
+ if (!(err instanceof VtigerError))
81
+ return false;
82
+ const code = err.code.toUpperCase();
83
+ return (code.includes("SESSION") ||
84
+ code === "AUTHENTICATION_REQUIRED" ||
85
+ code === "AUTHREQUIRED");
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Authentication
89
+ // ---------------------------------------------------------------------------
90
+ /** Run the challenge/response handshake and cache the session. */
91
+ async login() {
92
+ const challenge = await this.raw("getchallenge", {
93
+ username: this.config.username,
94
+ });
95
+ const token = challenge.token;
96
+ const accessKey = md5(token + this.config.accessKey);
97
+ const result = await this.raw("login", {
98
+ username: this.config.username,
99
+ accessKey,
100
+ });
101
+ this.session = result;
102
+ return result;
103
+ }
104
+ /** Ensure we have a live session (login lazily). */
105
+ async ensureSession() {
106
+ if (!this.session)
107
+ await this.login();
108
+ return this.session;
109
+ }
110
+ /**
111
+ * Authenticated request with one automatic re-login + retry if the
112
+ * session has expired. `operation` selects GET vs POST automatically.
113
+ */
114
+ async call(operation, params = {}) {
115
+ const session = await this.ensureSession();
116
+ try {
117
+ return await this.raw(operation, { sessionName: session.sessionName, ...params });
118
+ }
119
+ catch (err) {
120
+ if (this.isSessionError(err)) {
121
+ const fresh = await this.login();
122
+ return await this.raw(operation, { sessionName: fresh.sessionName, ...params });
123
+ }
124
+ throw err;
125
+ }
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Typed operation wrappers
129
+ // ---------------------------------------------------------------------------
130
+ /** Modules the current user may access. */
131
+ async listTypes() {
132
+ return this.call("listtypes");
133
+ }
134
+ /** Field metadata for a module (reflects the LIVE schema, incl. custom fields). */
135
+ async describe(elementType) {
136
+ return this.call("describe", { elementType });
137
+ }
138
+ /** Run a read-only VTQL SELECT. Returns an array of records. */
139
+ async query(vtql) {
140
+ return this.call("query", { query: vtql });
141
+ }
142
+ /** Fetch one record by its webservice id (e.g. "17x1234"). */
143
+ async retrieve(id) {
144
+ return this.call("retrieve", { id });
145
+ }
146
+ /** Create a record. `element` is a plain object of field => value. */
147
+ async create(elementType, element) {
148
+ return this.call("create", {
149
+ elementType,
150
+ element: JSON.stringify(element),
151
+ });
152
+ }
153
+ /** Full-record update (must include `id` and all mandatory fields). */
154
+ async update(element) {
155
+ return this.call("update", { element: JSON.stringify(element) });
156
+ }
157
+ /** Partial update β€” only the supplied fields change (must include `id`). */
158
+ async revise(element) {
159
+ return this.call("revise", { element: JSON.stringify(element) });
160
+ }
161
+ /** Delete a record by webservice id. Guarded by allowDelete. */
162
+ async delete(id) {
163
+ if (!this.config.allowDelete) {
164
+ throw new VtigerError("DELETE_DISABLED", "Deletion is disabled. Set VTIGER_ALLOW_DELETE=true to enable it.");
165
+ }
166
+ return this.call("delete", { id });
167
+ }
168
+ }
169
+ /** Build a client from the standard environment variables. */
170
+ export function createClientFromEnv() {
171
+ return new VtigerClient({
172
+ url: process.env.VTIGER_URL ?? "",
173
+ username: process.env.VTIGER_USERNAME ?? "",
174
+ accessKey: process.env.VTIGER_ACCESS_KEY ?? "",
175
+ allowDelete: (process.env.VTIGER_ALLOW_DELETE ?? "false").toLowerCase() === "true",
176
+ });
177
+ }
178
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { isWebserviceId } from "./vtiger-id.js";
2
+ /**
3
+ * Helpers for the daily timesheet module. The timesheet module in this
4
+ * portal is `TimeControl` (table vtiger_timecontrol) β€” NOT literally
5
+ * "Timesheet".
6
+ *
7
+ * The LIVE field set (verified via describe against production) is:
8
+ * date_start, date_end (date), time_start, time_end (time HH:MM:SS),
9
+ * related_to (-> ProjectTask), projectid (-> Project),
10
+ * description (activity text), hours_type (picklist: Regular/Extra Hours),
11
+ * assigned_user_id (owner, the only MANDATORY field),
12
+ * totaltime/total_hour/total_min (computed server-side from the times),
13
+ * tc_code (auto). We deliberately do NOT set the computed fields β€” the
14
+ * module's save_module() derives them and rolls the hours up onto the
15
+ * linked ProjectTask.
16
+ */
17
+ export const TIMESHEET_MODULE = "TimeControl";
18
+ export const TASK_MODULE = "ProjectTask";
19
+ export const PROJECT_MODULE = "Project";
20
+ /** Escape a value for safe inclusion inside a single-quoted VTQL string literal. */
21
+ export function escapeVtql(value) {
22
+ // VTQL is SQL-like; single quotes are the string delimiter. Double them to escape.
23
+ return value.replace(/'/g, "''");
24
+ }
25
+ /** Normalise a clock time to vtiger's "HH:MM:SS" form (accepts "H:MM" or "HH:MM:SS"). */
26
+ export function normalizeTime(t) {
27
+ const s = t.trim();
28
+ let m = /^(\d{1,2}):(\d{2}):(\d{2})$/.exec(s);
29
+ if (m)
30
+ return `${m[1].padStart(2, "0")}:${m[2]}:${m[3]}`;
31
+ m = /^(\d{1,2}):(\d{2})$/.exec(s);
32
+ if (m)
33
+ return `${m[1].padStart(2, "0")}:${m[2]}:00`;
34
+ throw new Error(`Invalid time "${t}" (expected HH:MM or HH:MM:SS)`);
35
+ }
36
+ /** Convert an explicit total ("H:MM" or a number of hours/seconds) to seconds. */
37
+ export function parseTotalToSeconds(total) {
38
+ const s = total.trim();
39
+ const hm = /^(\d{1,2}):(\d{2})$/.exec(s);
40
+ if (hm)
41
+ return Number(hm[1]) * 3600 + Number(hm[2]) * 60;
42
+ if (/^\d+$/.test(s))
43
+ return Number(s) * 60; // bare number => minutes
44
+ throw new Error(`Invalid total "${total}" (expected "H:MM" or minutes)`);
45
+ }
46
+ /**
47
+ * Resolve a project task reference (used for the timesheet `related_to`
48
+ * field). Accepts either a webservice id (returned as-is) or a name to
49
+ * search for. Returns all matches so the caller can disambiguate.
50
+ */
51
+ export async function findTasks(client, taskNameOrId) {
52
+ const trimmed = taskNameOrId.trim();
53
+ if (isWebserviceId(trimmed)) {
54
+ const rec = await client.retrieve(trimmed);
55
+ return [{ id: rec.id, name: rec.projecttaskname ?? trimmed, projectId: rec.projectid }];
56
+ }
57
+ const like = escapeVtql(trimmed);
58
+ const rows = await client.query(`SELECT id, projecttaskname, projectid FROM ${TASK_MODULE} WHERE projecttaskname LIKE '%${like}%' ORDER BY projecttaskname LIMIT 25;`);
59
+ return rows.map((r) => ({
60
+ id: r.id,
61
+ name: r.projecttaskname,
62
+ projectId: r.projectid,
63
+ }));
64
+ }
65
+ /**
66
+ * Assemble a TimeControl `element` object for `client.create`. Sets only
67
+ * the fields the caller supplied. When start/end times are given, the
68
+ * server computes totaltime/total_hour/total_min itself β€” so we leave
69
+ * those alone. An explicit `totalTime` (without times) is written to the
70
+ * seconds-based `totaltime` field.
71
+ */
72
+ export function buildTimesheetElement(input) {
73
+ const element = {
74
+ date_start: input.date,
75
+ date_end: input.date,
76
+ };
77
+ if (input.assignedUserId)
78
+ element.assigned_user_id = input.assignedUserId;
79
+ if (input.taskId)
80
+ element.related_to = input.taskId;
81
+ if (input.projectId)
82
+ element.projectid = input.projectId;
83
+ if (input.startTime)
84
+ element.time_start = normalizeTime(input.startTime);
85
+ if (input.endTime)
86
+ element.time_end = normalizeTime(input.endTime);
87
+ if (input.activity)
88
+ element.description = input.activity;
89
+ if (input.hoursType)
90
+ element.hours_type = input.hoursType;
91
+ // Only set totaltime when we can't rely on server computation from times.
92
+ if (input.totalTime && !(input.startTime && input.endTime)) {
93
+ element.totaltime = parseTotalToSeconds(input.totalTime);
94
+ }
95
+ return element;
96
+ }
97
+ /** List the authenticated user's timesheet entries within a date range. */
98
+ export async function listMyTimesheets(client, from, to) {
99
+ const clauses = [];
100
+ const uid = client.userId;
101
+ if (uid)
102
+ clauses.push(`assigned_user_id = '${uid}'`);
103
+ if (from)
104
+ clauses.push(`date_start >= '${escapeVtql(from)}'`);
105
+ if (to)
106
+ clauses.push(`date_start <= '${escapeVtql(to)}'`);
107
+ const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
108
+ return client.query(`SELECT id, tc_code, date_start, time_start, time_end, totaltime, description, hours_type, related_to FROM ${TIMESHEET_MODULE}${where} ORDER BY date_start DESC LIMIT 100;`);
109
+ }
110
+ //# sourceMappingURL=timesheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timesheet.js","sourceRoot":"","sources":["../../src/core/timesheet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * vtiger identifies records with a composite "webservice id" of the form
3
+ * `<moduleEntityId>x<recordId>`, e.g. "17x1234". These helpers make it
4
+ * safe to detect and work with that format.
5
+ */
6
+ const WS_ID_RE = /^\d+x\d+$/;
7
+ /** True if `value` looks like a vtiger webservice id (e.g. "17x1234"). */
8
+ export function isWebserviceId(value) {
9
+ return WS_ID_RE.test(value.trim());
10
+ }
11
+ /** Split a webservice id into its module and record parts. */
12
+ export function splitId(id) {
13
+ if (!isWebserviceId(id)) {
14
+ throw new Error(`Not a valid vtiger webservice id: "${id}" (expected "<num>x<num>")`);
15
+ }
16
+ const [moduleId, recordId] = id.split("x");
17
+ return { moduleId, recordId };
18
+ }
19
+ //# sourceMappingURL=vtiger-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vtiger-id.js","sourceRoot":"","sources":["../../src/core/vtiger-id.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,sCAAsC,EAAE,4BAA4B,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { createClientFromEnv } from "../core/client.js";
6
+ import { TIMESHEET_MODULE, PROJECT_MODULE, findTasks, buildTimesheetElement, listMyTimesheets, } from "../core/timesheet.js";
7
+ /**
8
+ * MCP stdio server that exposes the vtiger portal to an AI agent AS the
9
+ * configured developer. Credentials come from env (VTIGER_URL /
10
+ * VTIGER_USERNAME / VTIGER_ACCESS_KEY); vtiger enforces that developer's
11
+ * own row-level access, so every tool is inherently scoped to them.
12
+ */
13
+ // Lazily build the client so a missing-credential error surfaces as a clean
14
+ // tool error rather than crashing the transport at startup.
15
+ let cached = null;
16
+ function getClient() {
17
+ if (!cached)
18
+ cached = createClientFromEnv();
19
+ return cached;
20
+ }
21
+ function today() {
22
+ return new Date().toISOString().slice(0, 10);
23
+ }
24
+ function ok(data) {
25
+ const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
26
+ return { content: [{ type: "text", text }] };
27
+ }
28
+ function fail(err) {
29
+ const message = err instanceof Error ? err.message : String(err);
30
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
31
+ }
32
+ const server = new McpServer({ name: "vtiger-mcp", version: "0.1.0" });
33
+ /**
34
+ * Register a tool with uniform error handling. `server.tool` is cast to
35
+ * sidestep the SDK's heavily-overloaded typings; the runtime contract
36
+ * (name, description, zod shape, async handler -> {content,...}) is exact.
37
+ */
38
+ function tool(name, description, shape, handler) {
39
+ server.tool(name, description, shape, async (args) => {
40
+ try {
41
+ return await handler(args);
42
+ }
43
+ catch (err) {
44
+ return fail(err);
45
+ }
46
+ });
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Generic, any-module tools (subject to the developer's own permissions)
50
+ // ---------------------------------------------------------------------------
51
+ tool("list_modules", "List the vtiger modules (entity types) the authenticated developer is allowed to access.", {}, async () => ok(await getClient().listTypes()));
52
+ tool("describe_module", "Describe a module's live fields: names, labels, mandatory flags, types, picklist values and reference targets. Call this before create/update to learn the exact field names (the schema is customized).", { module: z.string().describe("Module name, e.g. 'TimeControl', 'ProjectTask', 'Contacts'.") }, async ({ module }) => ok(await getClient().describe(module)));
53
+ tool("query", "Run a read-only VTQL SELECT (SQL-like). Results are already filtered to what the developer may see. Example: SELECT tc_code, date_start, totaltime FROM TimeControl LIMIT 10;", { vtql: z.string().describe("A VTQL SELECT statement. Include a LIMIT. Must start with SELECT.") }, async ({ vtql }) => {
54
+ if (!/^\s*select\b/i.test(vtql)) {
55
+ throw new Error("Only read-only SELECT queries are allowed here. Use create/update tools to modify data.");
56
+ }
57
+ return ok(await getClient().query(vtql));
58
+ });
59
+ tool("retrieve_record", "Fetch one record by its vtiger webservice id (e.g. '17x1234').", { id: z.string().describe("Webservice id, format <moduleId>x<recordId>.") }, async ({ id }) => ok(await getClient().retrieve(id)));
60
+ tool("create_record", "Create a record in any module the developer can write to. Provide field values as an object; use describe_module first to get exact field names. Reference fields (owners, related records) take webservice ids.", {
61
+ module: z.string().describe("Target module name, e.g. 'ProjectTask'."),
62
+ values: z.record(z.any()).describe("Field name -> value map for the new record."),
63
+ }, async ({ module, values }) => ok(await getClient().create(module, values)));
64
+ tool("update_record", "Update fields on an existing record (partial update β€” only supplied fields change). Uses vtiger 'revise'.", {
65
+ id: z.string().describe("Webservice id of the record to update."),
66
+ values: z.record(z.any()).describe("Field name -> new value map."),
67
+ }, async ({ id, values }) => ok(await getClient().revise({ id, ...values })));
68
+ tool("delete_record", "Delete a record by webservice id. Disabled unless the server was started with VTIGER_ALLOW_DELETE=true.", { id: z.string().describe("Webservice id of the record to delete.") }, async ({ id }) => ok(await getClient().delete(id)));
69
+ // ---------------------------------------------------------------------------
70
+ // Timesheet convenience tools (TimeControl module)
71
+ // ---------------------------------------------------------------------------
72
+ tool("whoami", "Return the authenticated developer's vtiger user id and profile info.", {}, async () => {
73
+ const client = getClient();
74
+ await client.listTypes(); // forces login so userId is populated
75
+ let user = null;
76
+ try {
77
+ const rows = await client.query(`SELECT id, user_name, first_name, last_name, email1 FROM Users WHERE id = '${client.userId}';`);
78
+ user = rows[0] ?? null;
79
+ }
80
+ catch {
81
+ // Non-admin users cannot query the Users module; the userId alone is enough.
82
+ }
83
+ return ok({ userId: client.userId, user });
84
+ });
85
+ tool("list_my_projects", "List projects the developer can access (id + name), to pick a project for timesheet logging.", {}, async () => ok(await getClient().query(`SELECT id, projectname, projectstatus FROM ${PROJECT_MODULE} ORDER BY projectname LIMIT 100;`)));
86
+ tool("list_my_tasks", "List project tasks (id + name) the developer can access. Optionally filter by a project-task name fragment. The task id is what a timesheet's 'related_to' links to.", { nameContains: z.string().optional().describe("Optional substring to match against the task name.") }, async ({ nameContains }) => ok(await findTasks(getClient(), nameContains ?? "")));
87
+ tool("log_timesheet", "Log a daily timesheet entry in the TimeControl module for the developer. Resolves a task by name or id, defaults the date to today and the owner to the current user, and computes total time from start/end when not given.", {
88
+ date: z.string().optional().describe("Entry date YYYY-MM-DD. Defaults to today."),
89
+ task: z.string().optional().describe("Project task name (searched) or webservice id, linked as related_to."),
90
+ startTime: z.string().optional().describe("Clock start HH:MM, e.g. '10:00'."),
91
+ endTime: z.string().optional().describe("Clock end HH:MM, e.g. '13:00'."),
92
+ totalTime: z.string().optional().describe("Explicit total HH:MM; computed from start/end if omitted."),
93
+ activity: z.string().optional().describe("Free-text description of the work."),
94
+ hoursType: z.string().optional().describe("Picklist value, e.g. 'Regular Hours' or 'Extra Hours'."),
95
+ }, async (args) => {
96
+ const client = getClient();
97
+ await client.listTypes(); // ensure login -> userId available
98
+ let taskId;
99
+ if (args.task) {
100
+ const matches = await findTasks(client, args.task);
101
+ if (matches.length === 0) {
102
+ throw new Error(`No project task found matching "${args.task}". Use list_my_tasks to browse.`);
103
+ }
104
+ if (matches.length > 1) {
105
+ return ok({
106
+ needsDisambiguation: true,
107
+ message: `Multiple tasks match "${args.task}". Re-call log_timesheet with the exact task id.`,
108
+ candidates: matches,
109
+ });
110
+ }
111
+ taskId = matches[0].id;
112
+ }
113
+ const element = buildTimesheetElement({
114
+ date: args.date ?? today(),
115
+ taskId,
116
+ startTime: args.startTime,
117
+ endTime: args.endTime,
118
+ totalTime: args.totalTime,
119
+ activity: args.activity,
120
+ hoursType: args.hoursType,
121
+ assignedUserId: client.userId ?? undefined,
122
+ });
123
+ const created = await client.create(TIMESHEET_MODULE, element);
124
+ return ok({ created });
125
+ });
126
+ tool("list_my_timesheets", "List the developer's own timesheet entries, optionally within a date range (YYYY-MM-DD).", {
127
+ from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD."),
128
+ to: z.string().optional().describe("End date inclusive, YYYY-MM-DD."),
129
+ }, async ({ from, to }) => {
130
+ const client = getClient();
131
+ await client.listTypes(); // ensure login -> userId available
132
+ return ok(await listMyTimesheets(client, from, to));
133
+ });
134
+ tool("update_timesheet", "Update fields on an existing TimeControl timesheet entry (partial update).", {
135
+ id: z.string().describe("Webservice id of the TimeControl record."),
136
+ date: z.string().optional().describe("New date YYYY-MM-DD."),
137
+ startTime: z.string().optional(),
138
+ endTime: z.string().optional(),
139
+ totalTime: z.string().optional(),
140
+ activity: z.string().optional(),
141
+ hoursType: z.string().optional(),
142
+ }, async ({ id, date, startTime, endTime, totalTime, activity, hoursType }) => {
143
+ const values = {};
144
+ if (date) {
145
+ values.date_start = date;
146
+ values.date_end = date;
147
+ }
148
+ if (startTime)
149
+ values.time_start = startTime;
150
+ if (endTime)
151
+ values.time_end = endTime;
152
+ if (totalTime)
153
+ values.totaltime = totalTime;
154
+ if (activity)
155
+ values.description = activity;
156
+ if (hoursType)
157
+ values.hours_type = hoursType;
158
+ return ok(await getClient().revise({ id, ...values }));
159
+ });
160
+ async function main() {
161
+ const transport = new StdioServerTransport();
162
+ await server.connect(transport);
163
+ // stderr is safe for logs; stdout is the MCP channel.
164
+ process.stderr.write("vtiger-mcp server ready (stdio)\n");
165
+ }
166
+ main().catch((err) => {
167
+ process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
168
+ process.exit(1);
169
+ });
170
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "vtiger": {
4
+ "command": "node",
5
+ "args": ["C:\\path\\to\\vtiger-mcp\\dist\\mcp\\server.js"],
6
+ "env": {
7
+ "VTIGER_URL": "https://yourcrmdomain.com",
8
+ "VTIGER_USERNAME": "your.username",
9
+ "VTIGER_ACCESS_KEY": "your-16-char-access-key",
10
+ "VTIGER_ALLOW_DELETE": "false"
11
+ }
12
+ }
13
+ }
14
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "vtiger-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server + CLI for vtiger CRM β€” log timesheets and create/update records in any module via an AI agent, using each user's own webservice access key (row-level access preserved).",
5
+ "type": "module",
6
+ "bin": {
7
+ "vtiger": "dist/cli/index.js",
8
+ "vtiger-mcp": "dist/mcp/server.js"
9
+ },
10
+ "main": "dist/mcp/server.js",
11
+ "files": [
12
+ "dist",
13
+ "examples",
14
+ "SETUP.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "watch": "tsc --watch",
19
+ "mcp": "node dist/mcp/server.js",
20
+ "cli": "node dist/cli/index.js",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "keywords": [
28
+ "vtiger",
29
+ "vtiger-crm",
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "crm",
33
+ "timesheet",
34
+ "cli",
35
+ "ai",
36
+ "claude"
37
+ ],
38
+ "author": "ViralP17",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/ViralP17/vtiger-mcp.git"
43
+ },
44
+ "homepage": "https://github.com/ViralP17/vtiger-mcp#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/ViralP17/vtiger-mcp/issues"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.12.0",
50
+ "zod": "^3.23.8"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.0.0",
54
+ "typescript": "^5.6.0"
55
+ }
56
+ }