@timesheetai/mcp 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/client.d.ts +87 -0
- package/dist/client.js +75 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.js +82 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +149 -0
- package/dist/server.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ktria Limited
|
|
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,124 @@
|
|
|
1
|
+
# @timesheetai/mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
|
+
**TimesheetAI**. It lets AI agents (Codex, Claude Code, Claude Desktop, Cursor,
|
|
5
|
+
…) read your projects and log time on your behalf.
|
|
6
|
+
|
|
7
|
+
Motivating workflow: at the end of the day, ask your agent to summarize what it
|
|
8
|
+
worked on and have it call `add_time_entry` to record the time directly.
|
|
9
|
+
|
|
10
|
+
## Tools
|
|
11
|
+
|
|
12
|
+
| Tool | What it does |
|
|
13
|
+
| ---------------- | ------------------------------------------------------------------------- |
|
|
14
|
+
| `list_workspaces`| List the workspaces your API key can access. |
|
|
15
|
+
| `list_projects` | List active projects and their active tasks (gives you `projectId`/`taskId`). |
|
|
16
|
+
| `list_today` | List your time entries for a day (defaults to today). |
|
|
17
|
+
| `add_time_entry` | Log one entry: `projectId`, optional `taskId`, `date`, `start`, `end`, `description`. |
|
|
18
|
+
|
|
19
|
+
Times are **wall-clock** (`HH:mm`), dates are `YYYY-MM-DD`. "Today" defaults to
|
|
20
|
+
this machine's local date.
|
|
21
|
+
|
|
22
|
+
`add_time_entry` is **idempotent** on an optional `clientRefId` (UUID). The
|
|
23
|
+
client generates one per call automatically; to make a retry of the *same*
|
|
24
|
+
logical add safe, pass the same `clientRefId` again — the backend returns the
|
|
25
|
+
existing entry instead of creating a duplicate.
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
The server needs two settings:
|
|
30
|
+
|
|
31
|
+
| Setting | Env var | Default |
|
|
32
|
+
| -------------------- | ----------------------- | -------------------------------- |
|
|
33
|
+
| API key | `TIMESHEETAI_API_KEY` | _(required)_ |
|
|
34
|
+
| Backend base URL | `TIMESHEETAI_BASE_URL` | `https://alpha.timesheetai.app` |
|
|
35
|
+
|
|
36
|
+
Create an API key from your TimesheetAI account (Account Portal → API keys).
|
|
37
|
+
|
|
38
|
+
Set them via your MCP host's `env` block (recommended), or in
|
|
39
|
+
`~/.config/timesheetai/config.json`:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"apiKey": "ak_...",
|
|
44
|
+
"baseUrl": "https://alpha.timesheetai.app"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Environment variables take precedence over the config file.
|
|
49
|
+
|
|
50
|
+
## Host setup
|
|
51
|
+
|
|
52
|
+
> **Alpha release:** the package is published under the `alpha` npm dist-tag while it
|
|
53
|
+
> targets the alpha backend, so install it as **`@timesheetai/mcp@alpha`** (a plain
|
|
54
|
+
> `@timesheetai/mcp` has no `latest` yet). The alpha endpoint is the built-in default,
|
|
55
|
+
> so `TIMESHEETAI_BASE_URL` is optional below.
|
|
56
|
+
|
|
57
|
+
### Claude Desktop
|
|
58
|
+
|
|
59
|
+
`claude_desktop_config.json`:
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"timesheetai": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["-y", "@timesheetai/mcp@alpha"],
|
|
67
|
+
"env": {
|
|
68
|
+
"TIMESHEETAI_API_KEY": "ak_...",
|
|
69
|
+
"TIMESHEETAI_BASE_URL": "https://alpha.timesheetai.app"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Claude Code
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
claude mcp add timesheetai \
|
|
80
|
+
--env TIMESHEETAI_API_KEY=ak_... \
|
|
81
|
+
--env TIMESHEETAI_BASE_URL=https://alpha.timesheetai.app \
|
|
82
|
+
-- npx -y @timesheetai/mcp@alpha
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Codex CLI
|
|
86
|
+
|
|
87
|
+
`~/.codex/config.toml`:
|
|
88
|
+
|
|
89
|
+
```toml
|
|
90
|
+
[mcp_servers.timesheetai]
|
|
91
|
+
command = "npx"
|
|
92
|
+
args = ["-y", "@timesheetai/mcp@alpha"]
|
|
93
|
+
env = { TIMESHEETAI_API_KEY = "ak_...", TIMESHEETAI_BASE_URL = "https://alpha.timesheetai.app" }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Local development
|
|
97
|
+
|
|
98
|
+
Build, then run the compiled entrypoint directly:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm --filter @timesheetai/mcp build
|
|
102
|
+
TIMESHEETAI_API_KEY=ak_... TIMESHEETAI_BASE_URL=http://localhost:5173 \
|
|
103
|
+
node packages/mcp/dist/index.js
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Before the package is published to npm, wire it into Claude Code by pointing at
|
|
107
|
+
the **built `dist/` path** instead of `npx @timesheetai/mcp` (rebuild after any
|
|
108
|
+
source change). Create the API key from the dev Account Portal → API keys; a
|
|
109
|
+
key from your dev Clerk instance authenticates against the dev backend
|
|
110
|
+
(`http://localhost:5173`):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
claude mcp add timesheetai \
|
|
114
|
+
--env TIMESHEETAI_API_KEY=ak_... \
|
|
115
|
+
--env TIMESHEETAI_BASE_URL=http://localhost:5173 \
|
|
116
|
+
-- node /absolute/path/to/timesheet-app/packages/mcp/dist/index.js
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Inspect interactively with the MCP Inspector:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
TIMESHEETAI_API_KEY=ak_... TIMESHEETAI_BASE_URL=http://localhost:5173 \
|
|
123
|
+
npx @modelcontextprotocol/inspector node packages/mcp/dist/index.js
|
|
124
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core: HTTP client over the TimesheetAI agent API. Thin wrapper that
|
|
3
|
+
* attaches the API key, builds query strings, and turns the backend's
|
|
4
|
+
* { error: { code, message } } envelope into a typed ApiError.
|
|
5
|
+
*/
|
|
6
|
+
import type { Config } from "./config.js";
|
|
7
|
+
export declare class ApiError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly details?: unknown | undefined;
|
|
11
|
+
constructor(status: number, code: string, message: string, details?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
export type Workspace = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string | null;
|
|
17
|
+
role: string;
|
|
18
|
+
};
|
|
19
|
+
export type Task = {
|
|
20
|
+
id: number;
|
|
21
|
+
name: string;
|
|
22
|
+
defaultBillable: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type Project = {
|
|
25
|
+
id: number;
|
|
26
|
+
name: string;
|
|
27
|
+
defaultBillable: boolean;
|
|
28
|
+
tasks: Task[];
|
|
29
|
+
};
|
|
30
|
+
export type TimeEntry = {
|
|
31
|
+
id: number;
|
|
32
|
+
clientRefId: string;
|
|
33
|
+
date: string;
|
|
34
|
+
start: string;
|
|
35
|
+
end: string;
|
|
36
|
+
startTime: string;
|
|
37
|
+
endTime: string;
|
|
38
|
+
projectId: number | null;
|
|
39
|
+
taskId: number | null;
|
|
40
|
+
project: string | null;
|
|
41
|
+
task: string | null;
|
|
42
|
+
isBillable: boolean;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
export type AddTimeEntryInput = {
|
|
46
|
+
projectId: number;
|
|
47
|
+
taskId?: number;
|
|
48
|
+
date: string;
|
|
49
|
+
start: string;
|
|
50
|
+
end: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
isBillable?: boolean;
|
|
53
|
+
workspace?: string;
|
|
54
|
+
/** Reused across retries for idempotency; generated if omitted. */
|
|
55
|
+
clientRefId?: string;
|
|
56
|
+
};
|
|
57
|
+
export declare class TimesheetClient {
|
|
58
|
+
private readonly config;
|
|
59
|
+
constructor(config: Config);
|
|
60
|
+
private request;
|
|
61
|
+
listWorkspaces(): Promise<{
|
|
62
|
+
workspaces: Workspace[];
|
|
63
|
+
}>;
|
|
64
|
+
listProjects(workspace?: string): Promise<{
|
|
65
|
+
workspace: {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
};
|
|
69
|
+
projects: Project[];
|
|
70
|
+
}>;
|
|
71
|
+
listEntries(date: string, workspace?: string): Promise<{
|
|
72
|
+
workspace: {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
76
|
+
date: string;
|
|
77
|
+
entries: TimeEntry[];
|
|
78
|
+
}>;
|
|
79
|
+
addTimeEntry(input: AddTimeEntryInput): Promise<{
|
|
80
|
+
workspace: {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
84
|
+
entry: TimeEntry;
|
|
85
|
+
idempotent: boolean;
|
|
86
|
+
}>;
|
|
87
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core: HTTP client over the TimesheetAI agent API. Thin wrapper that
|
|
3
|
+
* attaches the API key, builds query strings, and turns the backend's
|
|
4
|
+
* { error: { code, message } } envelope into a typed ApiError.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
export class ApiError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
code;
|
|
10
|
+
details;
|
|
11
|
+
constructor(status, code, message, details) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.details = details;
|
|
16
|
+
this.name = "ApiError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function queryString(params) {
|
|
20
|
+
const usable = Object.entries(params).filter(([, v]) => v !== undefined && v !== "");
|
|
21
|
+
if (usable.length === 0)
|
|
22
|
+
return "";
|
|
23
|
+
const sp = new URLSearchParams();
|
|
24
|
+
for (const [k, v] of usable)
|
|
25
|
+
sp.set(k, v);
|
|
26
|
+
return `?${sp.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
export class TimesheetClient {
|
|
29
|
+
config;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
async request(path, init) {
|
|
34
|
+
let res;
|
|
35
|
+
try {
|
|
36
|
+
res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
37
|
+
...init,
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
40
|
+
...(init?.body ? { "Content-Type": "application/json" } : {}),
|
|
41
|
+
...init?.headers,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (cause) {
|
|
46
|
+
throw new ApiError(0, "network_error", `Could not reach TimesheetAI at ${this.config.baseUrl}: ${cause?.message ?? cause}`);
|
|
47
|
+
}
|
|
48
|
+
const json = (await res.json().catch(() => null));
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const err = json?.error;
|
|
51
|
+
throw new ApiError(res.status, err?.code ?? "error", err?.message ?? `Request failed with HTTP ${res.status}`, err);
|
|
52
|
+
}
|
|
53
|
+
return json;
|
|
54
|
+
}
|
|
55
|
+
listWorkspaces() {
|
|
56
|
+
return this.request("/api/mcp/v1/workspaces");
|
|
57
|
+
}
|
|
58
|
+
listProjects(workspace) {
|
|
59
|
+
return this.request(`/api/mcp/v1/projects${queryString({ workspace })}`);
|
|
60
|
+
}
|
|
61
|
+
listEntries(date, workspace) {
|
|
62
|
+
return this.request(`/api/mcp/v1/entries${queryString({ date, workspace })}`);
|
|
63
|
+
}
|
|
64
|
+
addTimeEntry(input) {
|
|
65
|
+
// Stable idempotency key per logical add: reuse a caller-supplied id, else
|
|
66
|
+
// generate one here (before the request) so a transport-level retry of THIS
|
|
67
|
+
// body hits the backend's idempotency path instead of duplicating.
|
|
68
|
+
const body = { ...input, clientRefId: input.clientRefId ?? randomUUID() };
|
|
69
|
+
return this.request("/api/mcp/v1/time-entries", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core: config + credential loading. Consumed by the MCP server now and
|
|
3
|
+
* the deferred CLI later. Credentials live outside any repo — env vars (set in
|
|
4
|
+
* the host's MCP config) or ~/.config/timesheetai/config.json.
|
|
5
|
+
*/
|
|
6
|
+
export type Config = {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
};
|
|
10
|
+
/** Alpha is the v1 target; override with TIMESHEETAI_BASE_URL for production. */
|
|
11
|
+
export declare const DEFAULT_BASE_URL = "https://alpha.timesheetai.app";
|
|
12
|
+
/**
|
|
13
|
+
* The canonical production host. Any other base URL is a non-production
|
|
14
|
+
* endpoint and is flagged in the startup banner so a tester is never silently
|
|
15
|
+
* pinned to alpha/local when they think they are on production (and vice versa).
|
|
16
|
+
*/
|
|
17
|
+
export declare const PRODUCTION_HOST = "timesheetai.app";
|
|
18
|
+
/** Classify a base URL for the startup banner. */
|
|
19
|
+
export declare function describeEndpoint(baseUrl: string): {
|
|
20
|
+
production: boolean;
|
|
21
|
+
label: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Resolve config. Precedence: environment variables > config file > defaults.
|
|
25
|
+
* Throws a clear error if no API key can be found.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadConfig(): Config;
|
|
28
|
+
/**
|
|
29
|
+
* The user's local calendar day as YYYY-MM-DD. Intentionally uses local Date
|
|
30
|
+
* methods: the MCP server runs on the user's machine, so its local day IS the
|
|
31
|
+
* user's day. The backend then applies wall-clock/floating-time rules to this
|
|
32
|
+
* explicit date and never guesses a timezone (story Open Design Decision #4).
|
|
33
|
+
*/
|
|
34
|
+
export declare function localToday(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core: config + credential loading. Consumed by the MCP server now and
|
|
3
|
+
* the deferred CLI later. Credentials live outside any repo — env vars (set in
|
|
4
|
+
* the host's MCP config) or ~/.config/timesheetai/config.json.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
/** Alpha is the v1 target; override with TIMESHEETAI_BASE_URL for production. */
|
|
10
|
+
export const DEFAULT_BASE_URL = "https://alpha.timesheetai.app";
|
|
11
|
+
/**
|
|
12
|
+
* The canonical production host. Any other base URL is a non-production
|
|
13
|
+
* endpoint and is flagged in the startup banner so a tester is never silently
|
|
14
|
+
* pinned to alpha/local when they think they are on production (and vice versa).
|
|
15
|
+
*/
|
|
16
|
+
export const PRODUCTION_HOST = "timesheetai.app";
|
|
17
|
+
/** Classify a base URL for the startup banner. */
|
|
18
|
+
export function describeEndpoint(baseUrl) {
|
|
19
|
+
let host;
|
|
20
|
+
try {
|
|
21
|
+
host = new URL(baseUrl).host.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { production: false, label: "invalid URL" };
|
|
25
|
+
}
|
|
26
|
+
if (host === PRODUCTION_HOST || host === `www.${PRODUCTION_HOST}`) {
|
|
27
|
+
return { production: true, label: "production" };
|
|
28
|
+
}
|
|
29
|
+
if (host === `alpha.${PRODUCTION_HOST}`) {
|
|
30
|
+
return { production: false, label: "ALPHA test endpoint" };
|
|
31
|
+
}
|
|
32
|
+
if (host === "localhost" || host.startsWith("localhost:") || host.startsWith("127.0.0.1")) {
|
|
33
|
+
return { production: false, label: "local dev endpoint" };
|
|
34
|
+
}
|
|
35
|
+
return { production: false, label: "non-production endpoint" };
|
|
36
|
+
}
|
|
37
|
+
function configFilePath() {
|
|
38
|
+
return (process.env.TIMESHEETAI_CONFIG ??
|
|
39
|
+
join(homedir(), ".config", "timesheetai", "config.json"));
|
|
40
|
+
}
|
|
41
|
+
function readConfigFile() {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(readFileSync(configFilePath(), "utf8"));
|
|
44
|
+
return {
|
|
45
|
+
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : undefined,
|
|
46
|
+
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {}; // file is optional
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve config. Precedence: environment variables > config file > defaults.
|
|
55
|
+
* Throws a clear error if no API key can be found.
|
|
56
|
+
*/
|
|
57
|
+
export function loadConfig() {
|
|
58
|
+
const file = readConfigFile();
|
|
59
|
+
const apiKey = process.env.TIMESHEETAI_API_KEY ?? file.apiKey;
|
|
60
|
+
if (!apiKey) {
|
|
61
|
+
throw new Error("Missing API key. Set TIMESHEETAI_API_KEY (recommended, in your MCP host config) " +
|
|
62
|
+
`or add { "apiKey": "..." } to ${configFilePath()}.`);
|
|
63
|
+
}
|
|
64
|
+
const baseUrl = (process.env.TIMESHEETAI_BASE_URL ??
|
|
65
|
+
file.baseUrl ??
|
|
66
|
+
DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
67
|
+
return { apiKey, baseUrl };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The user's local calendar day as YYYY-MM-DD. Intentionally uses local Date
|
|
71
|
+
* methods: the MCP server runs on the user's machine, so its local day IS the
|
|
72
|
+
* user's day. The backend then applies wall-clock/floating-time rules to this
|
|
73
|
+
* explicit date and never guesses a timezone (story Open Design Decision #4).
|
|
74
|
+
*/
|
|
75
|
+
export function localToday() {
|
|
76
|
+
const d = new Date();
|
|
77
|
+
const y = d.getFullYear();
|
|
78
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
79
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
80
|
+
return `${y}-${m}-${day}`;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point: connect the TimesheetAI MCP server over stdio. This is what an
|
|
4
|
+
* MCP host (Codex, Claude Code, Claude Desktop) launches via `npx @timesheetai/mcp`.
|
|
5
|
+
*/
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { createServer } from "./server.js";
|
|
8
|
+
import { describeEndpoint, loadConfig } from "./config.js";
|
|
9
|
+
async function main() {
|
|
10
|
+
// Load config once and inject it, so the banner and the client agree on the
|
|
11
|
+
// resolved base URL (no second file/env read).
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
const server = createServer(config);
|
|
14
|
+
const transport = new StdioServerTransport();
|
|
15
|
+
await server.connect(transport);
|
|
16
|
+
// stderr is safe for logs; stdout is the MCP transport channel.
|
|
17
|
+
// Echo the active endpoint so a tester is never silently on the wrong API —
|
|
18
|
+
// production is unmarked; alpha/local/other get a visible warning.
|
|
19
|
+
const ep = describeEndpoint(config.baseUrl);
|
|
20
|
+
const marker = ep.production ? "" : ` ⚠️ ${ep.label}`;
|
|
21
|
+
process.stderr.write(`TimesheetAI MCP → ${config.baseUrl}${marker}\n`);
|
|
22
|
+
}
|
|
23
|
+
main().catch((err) => {
|
|
24
|
+
process.stderr.write(`Fatal: ${err?.message ?? err}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,KAAK,UAAU,IAAI;IACjB,4EAA4E;IAC5E,+CAA+C;IAC/C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,gEAAgE;IAChE,4EAA4E;IAC5E,mEAAmE;IACnE,MAAM,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,MAAM,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TimesheetAI MCP server: exposes typed tools that AI agents call to read
|
|
3
|
+
* projects and log time. A thin client over the authenticated backend API.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { type Config } from "./config.js";
|
|
7
|
+
export declare function createServer(config?: Config): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TimesheetAI MCP server: exposes typed tools that AI agents call to read
|
|
3
|
+
* projects and log time. A thin client over the authenticated backend API.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { loadConfig, localToday } from "./config.js";
|
|
8
|
+
import { ApiError, TimesheetClient } from "./client.js";
|
|
9
|
+
function ok(data, summary) {
|
|
10
|
+
const text = summary
|
|
11
|
+
? `${summary}\n\n${JSON.stringify(data, null, 2)}`
|
|
12
|
+
: JSON.stringify(data, null, 2);
|
|
13
|
+
return { content: [{ type: "text", text }] };
|
|
14
|
+
}
|
|
15
|
+
function fail(err) {
|
|
16
|
+
if (err instanceof ApiError) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Error [${err.code}]: ${err.message}` }],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{ type: "text", text: `Error: ${err?.message ?? String(err)}` },
|
|
25
|
+
],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const TIME_DESC = "24-hour wall-clock time, HH:mm (e.g. 13:30).";
|
|
30
|
+
const WORKSPACE_DESC = "Workspace id, name, or slug. Optional when you belong to exactly one workspace.";
|
|
31
|
+
export function createServer(config = loadConfig()) {
|
|
32
|
+
const client = new TimesheetClient(config);
|
|
33
|
+
const server = new McpServer({
|
|
34
|
+
name: "timesheetai",
|
|
35
|
+
version: "0.1.0",
|
|
36
|
+
});
|
|
37
|
+
server.registerTool("list_workspaces", {
|
|
38
|
+
title: "List workspaces",
|
|
39
|
+
description: "List the TimesheetAI workspaces your API key can access. Use when a tool reports that a workspace must be specified.",
|
|
40
|
+
inputSchema: {},
|
|
41
|
+
}, async () => {
|
|
42
|
+
try {
|
|
43
|
+
return ok(await client.listWorkspaces());
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return fail(err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
server.registerTool("list_projects", {
|
|
50
|
+
title: "List projects",
|
|
51
|
+
description: "List active projects and their active tasks. Call this first to get the projectId and taskId needed by add_time_entry.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
workspace: z.string().describe(WORKSPACE_DESC).optional(),
|
|
54
|
+
},
|
|
55
|
+
}, async ({ workspace }) => {
|
|
56
|
+
try {
|
|
57
|
+
return ok(await client.listProjects(workspace));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return fail(err);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
server.registerTool("list_today", {
|
|
64
|
+
title: "List a day's time entries",
|
|
65
|
+
description: "List your own time entries for a given day. Defaults to today (this machine's local date) when no date is given.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
date: z
|
|
68
|
+
.string()
|
|
69
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
70
|
+
.describe("Day to list, YYYY-MM-DD. Defaults to today.")
|
|
71
|
+
.optional(),
|
|
72
|
+
workspace: z.string().describe(WORKSPACE_DESC).optional(),
|
|
73
|
+
},
|
|
74
|
+
}, async ({ date, workspace }) => {
|
|
75
|
+
try {
|
|
76
|
+
const day = date ?? localToday();
|
|
77
|
+
return ok(await client.listEntries(day, workspace));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return fail(err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
server.registerTool("add_time_entry", {
|
|
84
|
+
title: "Add a time entry",
|
|
85
|
+
description: "Log a single time entry. Resolve projectId/taskId via list_projects first. Time is wall-clock; the date defaults to today.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
projectId: z
|
|
88
|
+
.number()
|
|
89
|
+
.int()
|
|
90
|
+
.positive()
|
|
91
|
+
.describe("Project id from list_projects."),
|
|
92
|
+
taskId: z
|
|
93
|
+
.number()
|
|
94
|
+
.int()
|
|
95
|
+
.positive()
|
|
96
|
+
.describe("Task id from list_projects. Optional but recommended.")
|
|
97
|
+
.optional(),
|
|
98
|
+
date: z
|
|
99
|
+
.string()
|
|
100
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
101
|
+
.describe("Entry date, YYYY-MM-DD. Defaults to today.")
|
|
102
|
+
.optional(),
|
|
103
|
+
start: z
|
|
104
|
+
.string()
|
|
105
|
+
.regex(/^([01]\d|2[0-3]):[0-5]\d$/)
|
|
106
|
+
.describe(`Start ${TIME_DESC}`),
|
|
107
|
+
end: z
|
|
108
|
+
.string()
|
|
109
|
+
.regex(/^([01]\d|2[0-3]):[0-5]\d$/)
|
|
110
|
+
.describe(`End ${TIME_DESC} Must be after start.`),
|
|
111
|
+
description: z
|
|
112
|
+
.string()
|
|
113
|
+
.describe("What was worked on.")
|
|
114
|
+
.optional(),
|
|
115
|
+
isBillable: z
|
|
116
|
+
.boolean()
|
|
117
|
+
.describe("Override billable flag. Defaults to the task/project default.")
|
|
118
|
+
.optional(),
|
|
119
|
+
workspace: z.string().describe(WORKSPACE_DESC).optional(),
|
|
120
|
+
clientRefId: z
|
|
121
|
+
.string()
|
|
122
|
+
.uuid()
|
|
123
|
+
.describe("Idempotency key (UUID). Reuse the SAME value when retrying the same logical add so it is not duplicated. Omit to auto-generate (no retry protection).")
|
|
124
|
+
.optional(),
|
|
125
|
+
},
|
|
126
|
+
}, async ({ projectId, taskId, date, start, end, description, isBillable, workspace, clientRefId }) => {
|
|
127
|
+
try {
|
|
128
|
+
const result = await client.addTimeEntry({
|
|
129
|
+
projectId,
|
|
130
|
+
taskId,
|
|
131
|
+
date: date ?? localToday(),
|
|
132
|
+
start,
|
|
133
|
+
end,
|
|
134
|
+
description,
|
|
135
|
+
isBillable,
|
|
136
|
+
workspace,
|
|
137
|
+
clientRefId,
|
|
138
|
+
});
|
|
139
|
+
const verb = result.idempotent ? "Already logged" : "Logged";
|
|
140
|
+
const e = result.entry;
|
|
141
|
+
return ok(result, `${verb}: ${e.project ?? "?"} / ${e.task ?? "—"} on ${e.date} ${e.start}–${e.end}.`);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
return fail(err);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return server;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@timesheetai/mcp",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Model Context Protocol server for TimesheetAI — log time from AI agents.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"comment:dist-tag": "Published under the `alpha` npm dist-tag while DEFAULT_BASE_URL points at alpha. Install with `@timesheetai/mcp@alpha`. At production launch: flip DEFAULT_BASE_URL to production, bump to a stable version, and publish to `latest`.",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"tag": "alpha"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"timesheetai-mcp": "dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"start": "node dist/index.js",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepack": "pnpm build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
+
"zod": "^4.3.6"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.10.0",
|
|
36
|
+
"typescript": "^5.7.0"
|
|
37
|
+
}
|
|
38
|
+
}
|