New:Socket for Asana Is Now Available.Learn more
Get Started

@timewave-cloud/mcp

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@timewave-cloud/mcp - npm Package Compare versions

Comparing version
0.3.1
to
0.4.0
+1
-1
package.json
{
"name": "@timewave-cloud/mcp",
"version": "0.3.1",
"version": "0.4.0",
"description": "TimeWave MCP server — manage TimeWave tasks and the time tracker from an AI client (Claude Desktop, IDEs) over the Model Context Protocol.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -109,3 +109,37 @@ # TimeWave MCP server

| `delete_time_entry` | Permanently delete a time entry |
| `whoami` | Who the key belongs to, and which organization |
| `add_checklist_item` | Add a subtask to a task |
| `set_checklist_item` | Tick a subtask off (or back on) — `itemId` from `get_task` |
| `remove_checklist_item` | Remove a subtask |
| `list_timesheets` | Recent weeks: total, status, locked or not |
| `get_timesheet` | One week, day by day and project by project |
| `submit_timesheet` | Hand a week in — **this locks it** |
| `withdraw_timesheet` | Take a submitted week back (only before review) |
| `list_docs` | Search the knowledge base by title |
| `get_doc` | Read a document with its Markdown |
| `create_doc` | Write a new document |
| `update_doc` | Edit the title, body or visibility |
| `list_doc_versions` | A document's history — who changed what, when |
| `get_doc_version` | Read one earlier version in full |
## The knowledge base has its own locks
Two of them, and a key can hold the `docs` scope and still see nothing:
- **The organization has to open the base to assistants** — Settings →
Assistants. Off until somebody turns it on.
- **A single page can be held back**, along with everything filed under it. To
an assistant that page reads exactly as one that does not exist; that is the
point, and a `not found` is not something to route around.
New documents are **private** — visible only to their author. Pass
`visibility: "ORG"` when writing for the team.
`update_doc` replaces the whole body. The previous text is kept as a version and
can be read back with `list_doc_versions` / `get_doc_version`, but somebody has
to go and look for it — so change the part that is wrong rather than rewriting a
page from memory.
There is no tool for deleting a document, on purpose.
## Smart commits

@@ -112,0 +146,0 @@

@@ -59,6 +59,7 @@ #!/usr/bin/env node

// Surfaced to the client at connect time — orients an AI to how TimeWave works.
const INSTRUCTIONS = `TimeWave is a time-tracker + task manager (projects → kanban tasks → tracked time & money).
When asked to work in a TimeWave project: call \`guide\` first, then \`list_projects\` and \`list_tasks\` to see the board. Manage work with create_task / update_task / move_task / complete_task / comment_task, and record effort with start_timer/stop_timer or log_time.
const INSTRUCTIONS = `TimeWave is a time-tracker + task manager with a knowledge base (projects → kanban tasks → tracked time & money, plus Markdown docs).
When asked to work in a TimeWave project: call \`guide\` first, then \`list_projects\` and \`list_tasks\` to see the board. Manage work with create_task / update_task / move_task / complete_task / comment_task, tick subtasks off with set_checklist_item, and record effort with start_timer/stop_timer or log_time.
The knowledge base is \`list_docs\` / \`get_doc\` / \`create_doc\` / \`update_doc\`, and it is closed to assistants until an owner opens it.
If you also write code for the project, use SMART COMMITS: put the task key in the commit message. "closes WAVE-12" completes that task and moves it to the last column; "WAVE-12 #time 2h" logs time. A bare "WAVE-12" just links the commit. Call \`guide\` for the full rules.
Every action you take is attributed to the AI (an "AI" badge in the app).`;
Every action you take is attributed to the AI (an "AI" badge in the app).`

@@ -68,5 +69,8 @@ // Full reference returned by the `guide` tool.

TimeWave is a multi-tenant time tracker with a task manager. Structure:
**Organization → Projects → Tasks (on a kanban board) → tracked time (→ money)**.
TimeWave is a multi-tenant time tracker with a task manager and a knowledge base.
**Organization → Projects → Tasks (on a kanban board) → tracked time (→ money)**,
plus Markdown documents that hang off the organization or a project.
Call \`whoami\` if you need to know whose name will be on what you write.
## Typical workflow

@@ -79,2 +83,4 @@ 1. \`list_projects\` — projects and their kanban column names. Pick the project id.

- \`update_task\`, \`move_task\` (to a column by name/id), \`complete_task\`, \`comment_task\`, \`delete_task\`.
- Subtasks: \`add_checklist_item\`, \`set_checklist_item\` (tick one off — get \`itemId\` from \`get_task\`), \`remove_checklist_item\`.
If you wrote a checklist when you created the task, tick items as you finish them. A board showing 0/6 on work that is done is worse than no checklist.
4. Record effort:

@@ -85,2 +91,44 @@ - \`start_timer\` / \`stop_timer\` / \`timer_status\` for live tracking, or

## Timesheets
A week of one person's time, handed in for approval. \`list_timesheets\` shows the
last few weeks with their status and whether they are locked; \`get_timesheet\`
breaks one week down by day and project.
\`submit_timesheet\` **locks** the week — no time can be added or edited in it
afterwards. It is somebody's statement about their own work, so ask before
submitting one. \`withdraw_timesheet\` takes it back, but only while nobody has
reviewed it; an approved week needs an admin to reopen.
A locked week is also why adding time can be refused. If \`log_time\` says the
week is locked, that is the rule working, not an error to route around.
## Knowledge base
Markdown pages: \`list_docs\` (search titles), \`get_doc\` (one page with its text),
\`create_doc\`, \`update_doc\`, and the history — \`list_doc_versions\`,
\`get_doc_version\`.
Things worth knowing before you write:
- **It is closed to assistants until an owner opens it** in Settings → Knowledge
base, and this key also needs the \`docs\` scope. If you get told the base is
closed, say so and stop — that is a decision somebody made, not an obstacle.
- **Individual pages can be held back.** One that is reads exactly like a page
that does not exist. Do not treat a "not found" as something to work around.
- **New pages are PRIVATE** — visible only to their author. If you are writing
something for the team, pass \`visibility: "ORG"\`, or you have written a note
to one person and told them it is documentation.
- **\`update_doc\` replaces the whole body.** The previous text is kept as a
version, so nothing is lost — but a person has to go and find it, which they
will not think to do. Read the page first and change the part that is wrong,
rather than rewriting it from memory.
- **Do not put credentials in a document.** Passwords, tokens, private keys: a
knowledge base is built to be searched, shared and exported, which is exactly
what you do not want for a secret. Say where the secret lives instead.
- There is no delete tool, on purpose. Deleting documentation is not something
an assistant should be able to do in passing; ask a person to do it.
Attachments come back as metadata only. To fetch one, GET
\`/api/attachments/{id}\` with the same API key — not through this server, which
would put the whole file in the conversation.
## Smart commits (when you commit code to a connected repo)

@@ -94,3 +142,3 @@ Reference the task key (e.g. \`WAVE-12\`) in the commit message. On import (manual or the push webhook) TimeWave acts on it:

Rules & caveats:
- Actions are attributed to the **commit author** (matched to a member by email). If the git email isn't a member, the commit is still linked but smart actions are skipped.
- Actions are attributed to the **commit author** (matched by GitHub login first, then email). If neither maps to a member, the commit is still linked but smart actions are skipped.
- Actions run **once per commit** and only for commits authored **after** the repo was connected — a first import of history never retroactively closes tasks or logs time.

@@ -100,6 +148,8 @@ - Any message that *contains* the syntax triggers it — even one describing it. Don't put example directives in real commit messages.

## Attribution
Everything you do via this server is flagged \`viaApi\` and shows an **AI** badge in the app. Smart-commit actions are attributed to the commit author, not the AI.`;
Everything you do via this server is flagged \`viaApi\` and shows an **AI** badge in the app — on tasks, on time entries, and in a document's history. Smart-commit actions are attributed to the commit author, not the AI.
That badge is the reason to be careful with edits rather than a licence to be careless: people read a document as something a colleague wrote, and the badge is on the version, not on the page.`;
const server = new McpServer(
{ name: "timewave", version: "0.3.0" },
{ name: "timewave", version: "0.4.0" },
{ instructions: INSTRUCTIONS },

@@ -347,4 +397,204 @@ );

// --- who am I -------------------------------------------------------------
server.registerTool(
"whoami",
{
title: "Who this key belongs to",
description:
"The person and organization behind this key, and the org's default currency. Useful before writing anything: it says whose name will be on it.",
},
tool(() => call("GET", "/me")),
);
// --- checklists -----------------------------------------------------------
//
// A task's subtasks. Without these an assistant can write a checklist when it
// creates a task and then never tick anything off, which is how a board ends up
// showing 0/6 on work that is finished.
server.registerTool(
"add_checklist_item",
{
title: "Add a checklist item",
description: "Add a subtask to a task's checklist. Returns the whole task.",
inputSchema: { id: z.string().describe("Task id"), text: z.string() },
},
tool(({ id, text }) => call("POST", `/tasks/${id}/checklist`, { text })),
);
server.registerTool(
"set_checklist_item",
{
title: "Tick a checklist item off (or back on)",
description:
"Mark one checklist item done or not done. Get itemId from get_task. Returns the whole task.",
inputSchema: {
id: z.string().describe("Task id"),
itemId: z.string(),
done: z.boolean().optional().describe("Defaults to true"),
},
},
tool(({ id, itemId, done }) =>
call("PATCH", `/tasks/${id}/checklist/${itemId}`, { done: done ?? true }),
),
);
server.registerTool(
"remove_checklist_item",
{
title: "Remove a checklist item",
description: "Delete one item from a task's checklist. Returns the whole task.",
inputSchema: { id: z.string().describe("Task id"), itemId: z.string() },
},
tool(({ id, itemId }) => call("DELETE", `/tasks/${id}/checklist/${itemId}`)),
);
// --- timesheets -----------------------------------------------------------
server.registerTool(
"list_timesheets",
{
title: "Recent weeks and whether they are handed in",
description:
"Your last few weeks: total tracked, status (OPEN | SUBMITTED | APPROVED | REJECTED), and whether the week is locked. Includes weeks nobody has submitted.",
inputSchema: { limit: z.number().optional().describe("How many weeks back, counting this one (default 8)") },
},
tool(({ limit }) => call("GET", `/timesheets${limit != null ? `?limit=${limit}` : ""}`)),
);
server.registerTool(
"get_timesheet",
{
title: "One week's timesheet",
description:
"A week day by day and project by project, with its status. `week` is any date inside the week — the server normalises it to the Monday.",
inputSchema: { week: z.string().describe("ISO date inside the week, e.g. 2026-08-06") },
},
tool(({ week }) => call("GET", `/timesheets/${week}`)),
);
server.registerTool(
"submit_timesheet",
{
title: "Hand a week in",
description:
"Submit a week for approval. This LOCKS it: no time can be added or edited in that week afterwards. Ask the person first — it is their week and their statement about it.",
inputSchema: { week: z.string().describe("ISO date inside the week") },
},
tool(({ week }) => call("POST", `/timesheets/${week}/submit`)),
);
server.registerTool(
"withdraw_timesheet",
{
title: "Take a submitted week back",
description:
"Unlock a week that was handed in but not yet reviewed. An approved week cannot be withdrawn — an admin has to reopen it.",
inputSchema: { week: z.string().describe("ISO date inside the week") },
},
tool(({ week }) => call("POST", `/timesheets/${week}/withdraw`)),
);
// --- knowledge base -------------------------------------------------------
//
// Closed to assistants until an owner opens it in Settings → Knowledge base,
// and a key needs the `docs` scope. Individual pages can be held back, and a
// held-back page reads exactly like one that does not exist. That is by design;
// do not treat a "not found" here as a bug to route around.
server.registerTool(
"list_docs",
{
title: "Search the knowledge base",
description:
"Documents you can see, newest first, without their text. `q` matches the title. Use `parentId: \"root\"` for the top of the tree.",
inputSchema: {
q: z.string().optional().describe("Match on the title"),
projectId: z.string().optional(),
parentId: z.string().optional().describe('A document id, or "root" for top-level pages'),
limit: z.number().optional().describe("Default 50, max 200"),
},
},
tool(({ q, projectId, parentId, limit }) => {
const p = new URLSearchParams();
if (q) p.set("q", q);
if (projectId) p.set("projectId", projectId);
if (parentId) p.set("parentId", parentId);
if (limit != null) p.set("limit", String(limit));
const qs = p.toString();
return call("GET", `/docs${qs ? `?${qs}` : ""}`);
}),
);
server.registerTool(
"get_doc",
{
title: "Read a document",
description: "One document with its Markdown, who may see it, and its attachments.",
inputSchema: { id: z.string() },
},
tool(({ id }) => call("GET", `/docs/${id}`)),
);
server.registerTool(
"create_doc",
{
title: "Write a new document",
description:
"Create a page. PRIVATE unless you say otherwise — which means only its author will see it, so say `visibility: \"ORG\"` when you are writing something for the team.",
inputSchema: {
title: z.string(),
body: z.string().optional().describe("Markdown"),
visibility: z.enum(["PRIVATE", "RESTRICTED", "ORG"]).optional(),
kind: z.enum(["REFERENCE", "WIKI"]).optional(),
projectId: z.string().optional(),
parentId: z.string().optional().describe("File it under another document"),
},
},
tool((args) => call("POST", "/docs", args)),
);
server.registerTool(
"update_doc",
{
title: "Edit a document",
description:
"Change the title, the body, or who may see it. Only the fields you send are touched, so renaming does not need the body. The previous text is kept as a version — but a person still has to go and find it, so prefer editing the part that is wrong over replacing the page.",
inputSchema: {
id: z.string(),
title: z.string().optional(),
body: z.string().optional().describe("The full Markdown, replacing what is there"),
visibility: z.enum(["PRIVATE", "RESTRICTED", "ORG"]).optional().describe("Author only"),
},
},
tool(({ id, ...patch }) => call("PATCH", `/docs/${id}`, patch)),
);
server.registerTool(
"list_doc_versions",
{
title: "What a document said before",
description:
"The document's history, newest first, without the old text: who changed it, when, whether it was an assistant, and how long each version was.",
inputSchema: { id: z.string(), limit: z.number().optional() },
},
tool(({ id, limit }) =>
call("GET", `/docs/${id}/versions${limit != null ? `?limit=${limit}` : ""}`),
),
);
server.registerTool(
"get_doc_version",
{
title: "Read one old version",
description:
"An earlier version in full. To restore it, read it and send its text back through update_doc.",
inputSchema: { id: z.string().describe("Document id"), versionId: z.string() },
},
tool(({ id, versionId }) => call("GET", `/docs/${id}/versions/${versionId}`)),
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`TimeWave MCP server connected to ${BASE}`);