@dain-os/mcp-server 0.3.2 → 0.4.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/README.md +141 -141
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/tools/developer.d.ts.map +1 -1
- package/dist/tools/developer.js +196 -31
- package/dist/tools/developer.js.map +1 -1
- package/dist/tools/iam.d.ts +3 -0
- package/dist/tools/iam.d.ts.map +1 -0
- package/dist/tools/iam.js +38 -0
- package/dist/tools/iam.js.map +1 -0
- package/dist/tools/index.d.ts +5 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/pr-review.d.ts +3 -0
- package/dist/tools/pr-review.d.ts.map +1 -0
- package/dist/tools/pr-review.js +83 -0
- package/dist/tools/pr-review.js.map +1 -0
- package/dist/tools/products.d.ts +3 -0
- package/dist/tools/products.d.ts.map +1 -0
- package/dist/tools/products.js +37 -0
- package/dist/tools/products.js.map +1 -0
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +22 -1
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +101 -8
- package/dist/tools/tasks.js.map +1 -1
- package/package.json +39 -35
package/README.md
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
# @dain-os/mcp-server
|
|
2
|
-
|
|
3
|
-
MCP server for DainOS. Lets Claude Code and other MCP clients manage your projects, tasks, task comments, proposals (including content drafting), and persist + retrieve developer knowledge (changelog / sessions / KB).
|
|
4
|
-
|
|
5
|
-
## What it does
|
|
6
|
-
|
|
7
|
-
Wraps the DainOS Express API behind 36 tools:
|
|
8
|
-
|
|
9
|
-
| Domain | Tools |
|
|
10
|
-
|---|---|
|
|
11
|
-
| Projects | `list_projects`, `get_project`, `create_project`, `update_project`, `archive_project` |
|
|
12
|
-
| Tasks | `list_tasks`, `get_task`, `create_task`, `update_task`, `complete_task`, `archive_task` |
|
|
13
|
-
| Comments | `add_task_comment` |
|
|
14
|
-
| Developer KB (v0.2+) | `log_changelog_entry`, `log_session_context`, `log_knowledge_base_entry`, `list_recent_sessions`, `search_knowledge_base` |
|
|
15
|
-
| Proposals (v0.3+) | `list_proposals`, `get_proposal`, `create_proposal`, `update_proposal`, `set_proposal_status`, `delete_proposal` |
|
|
16
|
-
| Proposal options (v0.3+) | `list_proposal_options`, `create_proposal_option`, `update_proposal_option`, `set_recommended_option`, `delete_proposal_option` |
|
|
17
|
-
| Proposal content (v0.3+) | `get_proposal_content`, `describe_proposal_block_types`, `upsert_proposal_section`, `delete_proposal_section`, `upsert_proposal_block`, `delete_proposal_block`, `reorder_proposal_blocks`, `replace_proposal_content` |
|
|
18
|
-
|
|
19
|
-
Project / task / comment / proposal tools run as the user who minted the token, with tenant scoping enforced server-side. Developer KB tools are intentionally cross-tenant: they accept `project` as a required parameter and store entries in a shared developer schema (used by `/wrapup`, `/recap`, `/dev-kb`).
|
|
20
|
-
|
|
21
|
-
### Drafting proposal content
|
|
22
|
-
|
|
23
|
-
Proposal content lives in a structured JSON shape: `{ version: 2, sections: [{ id, key, title, blocks: ContentBlock[] }] }`. Eight block types are supported: `text`, `cards`, `diagram`, `chart`, `table`, `image`, `gantt`, `iframe`.
|
|
24
|
-
|
|
25
|
-
Start a drafting session by calling `describe_proposal_block_types` — it returns the full catalogue (schema + minimal example + when-to-use hints + gotchas like the iframe host allowlist). Then use the granular content tools to add or change one block at a time. Each granular tool ships only the bytes you are editing, so token cost stays low even for fine-grained edits.
|
|
26
|
-
|
|
27
|
-
For wholesale rewrites or regeneration, use `replace_proposal_content`.
|
|
28
|
-
|
|
29
|
-
`get_proposal` includes a computed `shareUrl` field. It is populated as `https://dainos.app/proposals/view/<token>` once the proposal has been transitioned to `sent` (or later: `viewed`, `accepted`). Draft proposals return `shareUrl: null` because the link is not yet meaningful.
|
|
30
|
-
|
|
31
|
-
## Setup
|
|
32
|
-
|
|
33
|
-
### 1. Mint a personal access token
|
|
34
|
-
|
|
35
|
-
Sign in to DainOS, go to **Settings -> API tokens**, click **New token**, give it a name (e.g. `claude-code-laptop`), and copy the value. The raw token is shown exactly once.
|
|
36
|
-
|
|
37
|
-
Tokens look like `dain_pat_<43 base64url chars>`.
|
|
38
|
-
|
|
39
|
-
### 2. Add the server to your MCP client
|
|
40
|
-
|
|
41
|
-
#### Claude Code
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
claude mcp add dainos -- npx -y @dain-os/mcp-server
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Then set the token in your environment (or pass it inline):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
export DAINOS_API_TOKEN=dain_pat_<your token>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Or, for one-machine setup, add it to your Claude Code config directly:
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"mcpServers": {
|
|
58
|
-
"dainos": {
|
|
59
|
-
"command": "npx",
|
|
60
|
-
"args": ["-y", "@dain-os/mcp-server"],
|
|
61
|
-
"env": {
|
|
62
|
-
"DAINOS_API_TOKEN": "dain_pat_<your token>"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 3. Restart your MCP client
|
|
70
|
-
|
|
71
|
-
Claude Code picks up new MCP servers on restart.
|
|
72
|
-
|
|
73
|
-
## Environment
|
|
74
|
-
|
|
75
|
-
| Variable | Required | Default | Notes |
|
|
76
|
-
|---|---|---|---|
|
|
77
|
-
| `DAINOS_API_TOKEN` | yes | n/a | Personal access token from `/settings/api-tokens` |
|
|
78
|
-
| `DAINOS_API_URL` | no | `https://api.dainos.app` | Override for local dev or staging |
|
|
79
|
-
|
|
80
|
-
## Usage examples
|
|
81
|
-
|
|
82
|
-
Once connected, ask Claude Code things like:
|
|
83
|
-
|
|
84
|
-
- "List my active projects in DainOS."
|
|
85
|
-
- "What tasks are assigned to me?"
|
|
86
|
-
- "Create a task on the Portunus project called 'wire up the new sidebar' due Friday."
|
|
87
|
-
- "Mark that task complete and add a comment that the PR is #312."
|
|
88
|
-
|
|
89
|
-
The model uses `list_projects` to find ids, `create_task` / `update_task` / `complete_task` to mutate state, and `add_task_comment` for follow-ups.
|
|
90
|
-
|
|
91
|
-
## Troubleshooting
|
|
92
|
-
|
|
93
|
-
### 401 Unauthorized
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
DainOS rejected the token (401). Mint a new one at https://dainos.app/settings/api-tokens
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The token is revoked, expired, or never existed. Visit Settings -> API tokens, mint a new one, and update your MCP config.
|
|
100
|
-
|
|
101
|
-
### 403 Forbidden
|
|
102
|
-
|
|
103
|
-
The token is valid but your account does not have permission for the operation (e.g. archiving a project you do not own).
|
|
104
|
-
|
|
105
|
-
### Tool is not visible to Claude Code
|
|
106
|
-
|
|
107
|
-
Restart Claude Code after `claude mcp add ...`. MCP servers are loaded once at startup.
|
|
108
|
-
|
|
109
|
-
## Local development
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
git clone https://github.com/dain-agency/dain-os
|
|
113
|
-
cd dain-os/packages/mcp-server
|
|
114
|
-
npm install
|
|
115
|
-
npm run build
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Then point your MCP client at the local build:
|
|
119
|
-
|
|
120
|
-
```json
|
|
121
|
-
{
|
|
122
|
-
"mcpServers": {
|
|
123
|
-
"dainos-local": {
|
|
124
|
-
"command": "node",
|
|
125
|
-
"args": ["/path/to/dain-os/packages/mcp-server/dist/index.js"],
|
|
126
|
-
"env": {
|
|
127
|
-
"DAINOS_API_TOKEN": "dain_pat_...",
|
|
128
|
-
"DAINOS_API_URL": "http://localhost:3001"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Why a small tool surface
|
|
136
|
-
|
|
137
|
-
The Notion MCP works well because it exposes a focused, opinionated set of operations rather than a one-to-one mirror of every API endpoint. The DainOS MCP server follows the same principle: project and task work covers the bulk of the day-to-day "keep things updated" use case, and the model picks the right verb every time. Tools for time entries, deals, proposals, and attachments are tracked for follow-up versions.
|
|
138
|
-
|
|
139
|
-
## Licence
|
|
140
|
-
|
|
141
|
-
Proprietary. Internal use by Dain only.
|
|
1
|
+
# @dain-os/mcp-server
|
|
2
|
+
|
|
3
|
+
MCP server for DainOS. Lets Claude Code and other MCP clients manage your projects, tasks, task comments, proposals (including content drafting), and persist + retrieve developer knowledge (changelog / sessions / KB).
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Wraps the DainOS Express API behind 36 tools:
|
|
8
|
+
|
|
9
|
+
| Domain | Tools |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Projects | `list_projects`, `get_project`, `create_project`, `update_project`, `archive_project` |
|
|
12
|
+
| Tasks | `list_tasks`, `get_task`, `create_task`, `update_task`, `complete_task`, `archive_task` |
|
|
13
|
+
| Comments | `add_task_comment` |
|
|
14
|
+
| Developer KB (v0.2+) | `log_changelog_entry`, `log_session_context`, `log_knowledge_base_entry`, `list_recent_sessions`, `search_knowledge_base` |
|
|
15
|
+
| Proposals (v0.3+) | `list_proposals`, `get_proposal`, `create_proposal`, `update_proposal`, `set_proposal_status`, `delete_proposal` |
|
|
16
|
+
| Proposal options (v0.3+) | `list_proposal_options`, `create_proposal_option`, `update_proposal_option`, `set_recommended_option`, `delete_proposal_option` |
|
|
17
|
+
| Proposal content (v0.3+) | `get_proposal_content`, `describe_proposal_block_types`, `upsert_proposal_section`, `delete_proposal_section`, `upsert_proposal_block`, `delete_proposal_block`, `reorder_proposal_blocks`, `replace_proposal_content` |
|
|
18
|
+
|
|
19
|
+
Project / task / comment / proposal tools run as the user who minted the token, with tenant scoping enforced server-side. Developer KB tools are intentionally cross-tenant: they accept `project` as a required parameter and store entries in a shared developer schema (used by `/wrapup`, `/recap`, `/dev-kb`).
|
|
20
|
+
|
|
21
|
+
### Drafting proposal content
|
|
22
|
+
|
|
23
|
+
Proposal content lives in a structured JSON shape: `{ version: 2, sections: [{ id, key, title, blocks: ContentBlock[] }] }`. Eight block types are supported: `text`, `cards`, `diagram`, `chart`, `table`, `image`, `gantt`, `iframe`.
|
|
24
|
+
|
|
25
|
+
Start a drafting session by calling `describe_proposal_block_types` — it returns the full catalogue (schema + minimal example + when-to-use hints + gotchas like the iframe host allowlist). Then use the granular content tools to add or change one block at a time. Each granular tool ships only the bytes you are editing, so token cost stays low even for fine-grained edits.
|
|
26
|
+
|
|
27
|
+
For wholesale rewrites or regeneration, use `replace_proposal_content`.
|
|
28
|
+
|
|
29
|
+
`get_proposal` includes a computed `shareUrl` field. It is populated as `https://dainos.app/proposals/view/<token>` once the proposal has been transitioned to `sent` (or later: `viewed`, `accepted`). Draft proposals return `shareUrl: null` because the link is not yet meaningful.
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
### 1. Mint a personal access token
|
|
34
|
+
|
|
35
|
+
Sign in to DainOS, go to **Settings -> API tokens**, click **New token**, give it a name (e.g. `claude-code-laptop`), and copy the value. The raw token is shown exactly once.
|
|
36
|
+
|
|
37
|
+
Tokens look like `dain_pat_<43 base64url chars>`.
|
|
38
|
+
|
|
39
|
+
### 2. Add the server to your MCP client
|
|
40
|
+
|
|
41
|
+
#### Claude Code
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add dainos -- npx -y @dain-os/mcp-server
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then set the token in your environment (or pass it inline):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export DAINOS_API_TOKEN=dain_pat_<your token>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, for one-machine setup, add it to your Claude Code config directly:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"dainos": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@dain-os/mcp-server"],
|
|
61
|
+
"env": {
|
|
62
|
+
"DAINOS_API_TOKEN": "dain_pat_<your token>"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Restart your MCP client
|
|
70
|
+
|
|
71
|
+
Claude Code picks up new MCP servers on restart.
|
|
72
|
+
|
|
73
|
+
## Environment
|
|
74
|
+
|
|
75
|
+
| Variable | Required | Default | Notes |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `DAINOS_API_TOKEN` | yes | n/a | Personal access token from `/settings/api-tokens` |
|
|
78
|
+
| `DAINOS_API_URL` | no | `https://api.dainos.app` | Override for local dev or staging |
|
|
79
|
+
|
|
80
|
+
## Usage examples
|
|
81
|
+
|
|
82
|
+
Once connected, ask Claude Code things like:
|
|
83
|
+
|
|
84
|
+
- "List my active projects in DainOS."
|
|
85
|
+
- "What tasks are assigned to me?"
|
|
86
|
+
- "Create a task on the Portunus project called 'wire up the new sidebar' due Friday."
|
|
87
|
+
- "Mark that task complete and add a comment that the PR is #312."
|
|
88
|
+
|
|
89
|
+
The model uses `list_projects` to find ids, `create_task` / `update_task` / `complete_task` to mutate state, and `add_task_comment` for follow-ups.
|
|
90
|
+
|
|
91
|
+
## Troubleshooting
|
|
92
|
+
|
|
93
|
+
### 401 Unauthorized
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
DainOS rejected the token (401). Mint a new one at https://dainos.app/settings/api-tokens
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The token is revoked, expired, or never existed. Visit Settings -> API tokens, mint a new one, and update your MCP config.
|
|
100
|
+
|
|
101
|
+
### 403 Forbidden
|
|
102
|
+
|
|
103
|
+
The token is valid but your account does not have permission for the operation (e.g. archiving a project you do not own).
|
|
104
|
+
|
|
105
|
+
### Tool is not visible to Claude Code
|
|
106
|
+
|
|
107
|
+
Restart Claude Code after `claude mcp add ...`. MCP servers are loaded once at startup.
|
|
108
|
+
|
|
109
|
+
## Local development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/dain-agency/dain-os
|
|
113
|
+
cd dain-os/packages/mcp-server
|
|
114
|
+
npm install
|
|
115
|
+
npm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then point your MCP client at the local build:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"dainos-local": {
|
|
124
|
+
"command": "node",
|
|
125
|
+
"args": ["/path/to/dain-os/packages/mcp-server/dist/index.js"],
|
|
126
|
+
"env": {
|
|
127
|
+
"DAINOS_API_TOKEN": "dain_pat_...",
|
|
128
|
+
"DAINOS_API_URL": "http://localhost:3001"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Why a small tool surface
|
|
136
|
+
|
|
137
|
+
The Notion MCP works well because it exposes a focused, opinionated set of operations rather than a one-to-one mirror of every API endpoint. The DainOS MCP server follows the same principle: project and task work covers the bulk of the day-to-day "keep things updated" use case, and the model picks the right verb every time. Tools for time entries, deals, proposals, and attachments are tracked for follow-up versions.
|
|
138
|
+
|
|
139
|
+
## Licence
|
|
140
|
+
|
|
141
|
+
Proprietary. Internal use by Dain only.
|
package/dist/index.js
CHANGED
|
@@ -60,13 +60,15 @@ async function main() {
|
|
|
60
60
|
}
|
|
61
61
|
try {
|
|
62
62
|
const result = await tool.handler(client, parsed.data);
|
|
63
|
+
// Some endpoints (e.g. archive_project hits DELETE → 204) resolve to
|
|
64
|
+
// undefined. JSON.stringify(undefined) yields undefined, which fails the
|
|
65
|
+
// MCP SDK's content[0].text string validation. Emit a confirmation
|
|
66
|
+
// envelope instead so 204-style mutations don't crash the response.
|
|
67
|
+
const text = result === undefined
|
|
68
|
+
? JSON.stringify({ ok: true }, null, 2)
|
|
69
|
+
: JSON.stringify(result, null, 2);
|
|
63
70
|
return {
|
|
64
|
-
content: [
|
|
65
|
-
{
|
|
66
|
-
type: 'text',
|
|
67
|
-
text: JSON.stringify(result, null, 2),
|
|
68
|
-
},
|
|
69
|
-
],
|
|
71
|
+
content: [{ type: 'text', text }],
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
catch (error) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"developer.d.ts","sourceRoot":"","sources":["../../src/tools/developer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"developer.d.ts","sourceRoot":"","sources":["../../src/tools/developer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwXjD,eAAO,MAAM,cAAc,EAAE,cAAc,EAU1C,CAAC"}
|
package/dist/tools/developer.js
CHANGED
|
@@ -13,6 +13,38 @@ import { z } from 'zod';
|
|
|
13
13
|
* "herbert", "mabel") on every call. The backing tables live in the developer
|
|
14
14
|
* schema and span tenants.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Some MCP clients serialise array-typed tool arguments as JSON-encoded strings
|
|
18
|
+
* rather than nested arrays. Caught during /wrapup on 2026-05-14:
|
|
19
|
+
* `log_changelog_entry` rejected a perfectly-shaped batch with "expected array,
|
|
20
|
+
* received string" while `log_knowledge_base_entry` accepted the same shape.
|
|
21
|
+
* The difference was the model's serialisation choice on that specific tool
|
|
22
|
+
* call, not the schema. Defending the entries field at the parse boundary
|
|
23
|
+
* makes both spellings work.
|
|
24
|
+
*/
|
|
25
|
+
function jsonStringArray(item, min = 1, max = 50) {
|
|
26
|
+
return z.preprocess((val) => {
|
|
27
|
+
if (typeof val === 'string') {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(val);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return val;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
}, z.array(item).min(min).max(max));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Optional string that also accepts `null`. Some clients send `null` for
|
|
40
|
+
* "field intentionally absent" rather than omitting the key; the previous
|
|
41
|
+
* `z.string().optional()` rejected null with "expected string, received null".
|
|
42
|
+
*/
|
|
43
|
+
const optionalString = z.preprocess((val) => (val === null ? undefined : val), z.string().optional());
|
|
44
|
+
/** Optional non-string field (e.g. number, boolean, array) that accepts null too. */
|
|
45
|
+
function nullishOptional(schema) {
|
|
46
|
+
return z.preprocess((val) => (val === null ? undefined : val), schema.optional());
|
|
47
|
+
}
|
|
16
48
|
const commitTypeEnum = z.enum([
|
|
17
49
|
'feat', 'fix', 'chore', 'refactor', 'test', 'docs', 'perf', 'ci', 'build', 'revert', 'style',
|
|
18
50
|
]);
|
|
@@ -27,30 +59,30 @@ const kbCategoryEnum = z.enum(['gotcha', 'pattern', 'lesson', 'decision', 'worka
|
|
|
27
59
|
// ---------------------------------------------------------------------------
|
|
28
60
|
const changelogEntrySchema = z.object({
|
|
29
61
|
commit_sha: z.string().length(40).describe('Full 40-character SHA.'),
|
|
30
|
-
branch:
|
|
62
|
+
branch: optionalString.describe('Defaults to "main" if omitted.'),
|
|
31
63
|
commit_type: commitTypeEnum.describe('Conventional-commit type prefix.'),
|
|
32
|
-
scope:
|
|
64
|
+
scope: optionalString,
|
|
33
65
|
summary: z.string().describe('Commit message subject line.'),
|
|
34
|
-
description:
|
|
35
|
-
files_changed: z.coerce.number().int()
|
|
36
|
-
insertions: z.coerce.number().int()
|
|
37
|
-
deletions: z.coerce.number().int()
|
|
38
|
-
milestone:
|
|
39
|
-
task_ref:
|
|
40
|
-
impacts_config: z.boolean()
|
|
41
|
-
breaking_change: z.boolean()
|
|
42
|
-
tags: z.array(z.string())
|
|
43
|
-
author:
|
|
66
|
+
description: optionalString,
|
|
67
|
+
files_changed: nullishOptional(z.coerce.number().int()),
|
|
68
|
+
insertions: nullishOptional(z.coerce.number().int()),
|
|
69
|
+
deletions: nullishOptional(z.coerce.number().int()),
|
|
70
|
+
milestone: optionalString,
|
|
71
|
+
task_ref: optionalString.describe('Linear / Jira / Notion task id.'),
|
|
72
|
+
impacts_config: nullishOptional(z.boolean()).describe('Defaults false.'),
|
|
73
|
+
breaking_change: nullishOptional(z.boolean()).describe('Defaults false.'),
|
|
74
|
+
tags: nullishOptional(z.array(z.string())),
|
|
75
|
+
author: optionalString,
|
|
44
76
|
committed_at: z.string().describe('ISO 8601 timestamp.'),
|
|
45
|
-
pr_number: z.coerce.number().int()
|
|
46
|
-
pr_url:
|
|
77
|
+
pr_number: nullishOptional(z.coerce.number().int()),
|
|
78
|
+
pr_url: optionalString,
|
|
47
79
|
});
|
|
48
80
|
const logChangelogEntry = {
|
|
49
81
|
name: 'log_changelog_entry',
|
|
50
82
|
description: 'Append commit records to the developer changelog for a project. Use during /wrapup to persist today\'s commits. Accepts a batch (1-50 entries). Idempotent on (project, commit_sha): duplicates are silently skipped. Returns { inserted, skipped } counts.',
|
|
51
83
|
inputSchema: z.object({
|
|
52
84
|
project: z.string().describe('Project slug, e.g. "dain-os", "herbert", "mabel".'),
|
|
53
|
-
entries:
|
|
85
|
+
entries: jsonStringArray(changelogEntrySchema, 1, 50),
|
|
54
86
|
}),
|
|
55
87
|
handler: async (client, input) => {
|
|
56
88
|
const entries = input.entries.map((entry) => ({
|
|
@@ -73,17 +105,21 @@ const logSessionContext = {
|
|
|
73
105
|
inputSchema: z.object({
|
|
74
106
|
project: z.string().describe('Project slug, e.g. "dain-os".'),
|
|
75
107
|
session_name: z.string().describe('Short descriptive title.'),
|
|
76
|
-
session_date:
|
|
108
|
+
session_date: optionalString.describe('YYYY-MM-DD. Defaults to today server-side.'),
|
|
77
109
|
operator: z.string().describe('e.g. "Dane + Claude Opus 4.7".'),
|
|
78
|
-
machine:
|
|
79
|
-
duration_minutes: z.coerce.number().int()
|
|
110
|
+
machine: optionalString,
|
|
111
|
+
duration_minutes: nullishOptional(z.coerce.number().int()),
|
|
80
112
|
summary: z.string().describe('2-4 sentence overview of WHAT and WHY.'),
|
|
81
|
-
decisions_made: z.array(decisionSchema)
|
|
82
|
-
files_touched: z.array(z.string())
|
|
83
|
-
tasks_completed: z.array(z.string())
|
|
84
|
-
blockers: z.array(z.string())
|
|
85
|
-
handoff_notes:
|
|
86
|
-
tags: z.array(z.string())
|
|
113
|
+
decisions_made: nullishOptional(z.array(decisionSchema)).describe('Non-obvious choices future sessions need to understand.'),
|
|
114
|
+
files_touched: nullishOptional(z.array(z.string())),
|
|
115
|
+
tasks_completed: nullishOptional(z.array(z.string())),
|
|
116
|
+
blockers: nullishOptional(z.array(z.string())),
|
|
117
|
+
handoff_notes: optionalString.describe('What the next session needs to know first.'),
|
|
118
|
+
tags: nullishOptional(z.array(z.string())),
|
|
119
|
+
client_visible: nullishOptional(z.boolean()).describe('When false, the session is hidden from portal product pages and the client portal activity feed. Defaults to true. Set false for sensitive sessions (security incidents, internal escalations).'),
|
|
120
|
+
product_id: nullishOptional(z.string().uuid()).describe('UUID of the product this session relates to.'),
|
|
121
|
+
task_ids: nullishOptional(z.array(z.string().uuid()).max(100)).describe('UUIDs of DainOS tasks worked on during the session (max 100).'),
|
|
122
|
+
operator_iam_user_id: nullishOptional(z.string().uuid()).describe('IAM user UUID of the operator. Resolved server-side from the auth token when omitted.'),
|
|
87
123
|
}),
|
|
88
124
|
handler: async (client, input) => client.post('/developer/sessions', input),
|
|
89
125
|
};
|
|
@@ -95,20 +131,20 @@ const kbEntrySchema = z.object({
|
|
|
95
131
|
module: z.string().describe('System / area, e.g. "auth", "react", "prisma".'),
|
|
96
132
|
title: z.string().describe('Short descriptive title.'),
|
|
97
133
|
description: z.string().describe('What happened / what is this.'),
|
|
98
|
-
impact:
|
|
99
|
-
prevention:
|
|
100
|
-
severity: severityEnum
|
|
134
|
+
impact: optionalString.describe('What goes wrong if you don\'t know this.'),
|
|
135
|
+
prevention: optionalString.describe('How to avoid it next time.'),
|
|
136
|
+
severity: nullishOptional(severityEnum),
|
|
101
137
|
source_type: sourceTypeEnum,
|
|
102
|
-
source_refs: z.array(z.string())
|
|
103
|
-
tags: z.array(z.string())
|
|
104
|
-
platform:
|
|
138
|
+
source_refs: nullishOptional(z.array(z.string())).describe('Links, file paths, or commit SHAs.'),
|
|
139
|
+
tags: nullishOptional(z.array(z.string())),
|
|
140
|
+
platform: optionalString.describe('e.g. "supabase", "anthropic", "vercel".'),
|
|
105
141
|
});
|
|
106
142
|
const logKnowledgeBaseEntry = {
|
|
107
143
|
name: 'log_knowledge_base_entry',
|
|
108
144
|
description: 'Append KB findings (gotchas, patterns, lessons, decisions, workarounds) for a project. Only add entries for things that were SURPRISING, NON-OBVIOUS, or caused real problems. Use during /wrapup. Accepts a batch (1-50 entries). Check for similar existing titles via search_knowledge_base before adding to avoid duplicates.',
|
|
109
145
|
inputSchema: z.object({
|
|
110
146
|
project: z.string().describe('Project slug, e.g. "dain-os". Use "universal" for cross-product lessons.'),
|
|
111
|
-
entries:
|
|
147
|
+
entries: jsonStringArray(kbEntrySchema, 1, 50),
|
|
112
148
|
}),
|
|
113
149
|
handler: async (client, input) => {
|
|
114
150
|
const entries = input.entries.map((entry) => ({
|
|
@@ -172,11 +208,140 @@ const searchKnowledgeBase = {
|
|
|
172
208
|
return client.get(`/developer/knowledge-base/search?${params.toString()}`);
|
|
173
209
|
},
|
|
174
210
|
};
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// update_* — partial edits to existing developer KB rows
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
//
|
|
215
|
+
// Wraps PATCH /developer/{changelog|sessions|knowledge-base}/:id. Used when a
|
|
216
|
+
// /wrapup-style flow needs to amend a previously-logged entry (e.g. the user
|
|
217
|
+
// corrects a session summary, fills in handoff_notes after the fact, or
|
|
218
|
+
// reclassifies a KB entry's severity). `project` is immutable on every row —
|
|
219
|
+
// to "move" between projects, delete and re-insert.
|
|
220
|
+
const updateChangelogEntry = {
|
|
221
|
+
name: 'update_changelog_entry',
|
|
222
|
+
description: 'Partially update an existing changelog entry by id. Every field is optional; only the supplied fields are written. `project` and `commit_sha` are immutable. Returns the refreshed row. 404 if id is unknown.',
|
|
223
|
+
inputSchema: z.object({
|
|
224
|
+
id: z.string().uuid().describe('Changelog entry id (UUID).'),
|
|
225
|
+
branch: optionalString,
|
|
226
|
+
commit_type: commitTypeEnum.optional(),
|
|
227
|
+
scope: optionalString,
|
|
228
|
+
summary: optionalString,
|
|
229
|
+
description: optionalString,
|
|
230
|
+
files_changed: nullishOptional(z.coerce.number().int()),
|
|
231
|
+
insertions: nullishOptional(z.coerce.number().int()),
|
|
232
|
+
deletions: nullishOptional(z.coerce.number().int()),
|
|
233
|
+
milestone: optionalString,
|
|
234
|
+
task_ref: optionalString,
|
|
235
|
+
impacts_config: nullishOptional(z.boolean()),
|
|
236
|
+
breaking_change: nullishOptional(z.boolean()),
|
|
237
|
+
tags: nullishOptional(z.array(z.string())),
|
|
238
|
+
author: optionalString,
|
|
239
|
+
committed_at: optionalString.describe('ISO 8601 timestamp.'),
|
|
240
|
+
pr_number: nullishOptional(z.coerce.number().int()),
|
|
241
|
+
pr_url: optionalString,
|
|
242
|
+
}),
|
|
243
|
+
handler: async (client, input) => {
|
|
244
|
+
const { id, ...rest } = input;
|
|
245
|
+
// Drop undefined keys so the API only receives the fields the caller set.
|
|
246
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
|
|
247
|
+
return client.patch(`/developer/changelog/${id}`, body);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const updateSessionContext = {
|
|
251
|
+
name: 'update_session_context',
|
|
252
|
+
description: 'Partially update an existing session_context row by id. Every field is optional. `project` is immutable. Returns the refreshed row. 404 if id is unknown.',
|
|
253
|
+
inputSchema: z.object({
|
|
254
|
+
id: z.string().uuid().describe('Session context id (UUID).'),
|
|
255
|
+
session_name: optionalString,
|
|
256
|
+
session_date: optionalString.describe('YYYY-MM-DD.'),
|
|
257
|
+
operator: optionalString,
|
|
258
|
+
machine: optionalString,
|
|
259
|
+
duration_minutes: nullishOptional(z.coerce.number().int()),
|
|
260
|
+
summary: optionalString,
|
|
261
|
+
decisions_made: nullishOptional(z.array(decisionSchema)),
|
|
262
|
+
files_touched: nullishOptional(z.array(z.string())),
|
|
263
|
+
tasks_completed: nullishOptional(z.array(z.string())),
|
|
264
|
+
blockers: nullishOptional(z.array(z.string())),
|
|
265
|
+
handoff_notes: optionalString,
|
|
266
|
+
tags: nullishOptional(z.array(z.string())),
|
|
267
|
+
product_id: nullishOptional(z.string().uuid()).describe('UUID of the product this session relates to.'),
|
|
268
|
+
task_ids: nullishOptional(z.array(z.string().uuid()).max(100)).describe('UUIDs of DainOS tasks worked on during the session (max 100).'),
|
|
269
|
+
operator_iam_user_id: nullishOptional(z.string().uuid()).describe('IAM user UUID of the operator.'),
|
|
270
|
+
}),
|
|
271
|
+
handler: async (client, input) => {
|
|
272
|
+
const { id, ...rest } = input;
|
|
273
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
|
|
274
|
+
return client.patch(`/developer/sessions/${id}`, body);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
const updateKnowledgeBaseEntry = {
|
|
278
|
+
name: 'update_knowledge_base_entry',
|
|
279
|
+
description: 'Partially update an existing KB entry by id. Every field is optional. `project` is immutable. Returns the refreshed row. 404 if id is unknown.',
|
|
280
|
+
inputSchema: z.object({
|
|
281
|
+
id: z.string().uuid().describe('KB entry id (UUID).'),
|
|
282
|
+
category: kbCategoryEnum.optional(),
|
|
283
|
+
module: optionalString,
|
|
284
|
+
title: optionalString,
|
|
285
|
+
description: optionalString,
|
|
286
|
+
impact: optionalString,
|
|
287
|
+
prevention: optionalString,
|
|
288
|
+
severity: nullishOptional(severityEnum),
|
|
289
|
+
source_type: sourceTypeEnum.optional(),
|
|
290
|
+
source_refs: nullishOptional(z.array(z.string())),
|
|
291
|
+
tags: nullishOptional(z.array(z.string())),
|
|
292
|
+
platform: optionalString,
|
|
293
|
+
}),
|
|
294
|
+
handler: async (client, input) => {
|
|
295
|
+
const { id, ...rest } = input;
|
|
296
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
|
|
297
|
+
return client.patch(`/developer/knowledge-base/${id}`, body);
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// list_changelog — read changelog entries by project slug + date
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
const listChangelog = {
|
|
304
|
+
name: 'list_changelog',
|
|
305
|
+
description: 'List developer changelog entries for a project, newest first. Optionally filter by `since` (ISO 8601 date). Default limit 50, max 200. Use during /recap to review recent commits for a project without raw SQL.',
|
|
306
|
+
inputSchema: z.object({
|
|
307
|
+
project: z
|
|
308
|
+
.string()
|
|
309
|
+
.min(1)
|
|
310
|
+
.max(100)
|
|
311
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/, 'project must be a lowercase slug')
|
|
312
|
+
.describe('Project slug, e.g. "dain-os", "herbert", "mabel".'),
|
|
313
|
+
since: z
|
|
314
|
+
.string()
|
|
315
|
+
.datetime({ offset: true, message: 'since must be ISO 8601 with offset' })
|
|
316
|
+
.optional()
|
|
317
|
+
.describe('ISO 8601 timestamp with offset (e.g. 2026-05-18T00:00:00Z) — only return entries with committed_at >= this value.'),
|
|
318
|
+
limit: z.coerce
|
|
319
|
+
.number()
|
|
320
|
+
.int()
|
|
321
|
+
.min(1)
|
|
322
|
+
.max(200)
|
|
323
|
+
.optional()
|
|
324
|
+
.describe('Default 50, max 200.'),
|
|
325
|
+
}),
|
|
326
|
+
handler: async (client, input) => {
|
|
327
|
+
const params = new URLSearchParams();
|
|
328
|
+
params.set('project', input.project);
|
|
329
|
+
if (input.since)
|
|
330
|
+
params.set('since', input.since);
|
|
331
|
+
if (input.limit !== undefined)
|
|
332
|
+
params.set('limit', String(input.limit));
|
|
333
|
+
return client.get(`/developer/changelog?${params.toString()}`);
|
|
334
|
+
},
|
|
335
|
+
};
|
|
175
336
|
export const developerTools = [
|
|
176
337
|
logChangelogEntry,
|
|
177
338
|
logSessionContext,
|
|
178
339
|
logKnowledgeBaseEntry,
|
|
179
340
|
listRecentSessions,
|
|
180
341
|
searchKnowledgeBase,
|
|
342
|
+
updateChangelogEntry,
|
|
343
|
+
updateSessionContext,
|
|
344
|
+
updateKnowledgeBaseEntry,
|
|
345
|
+
listChangelog,
|
|
181
346
|
];
|
|
182
347
|
//# sourceMappingURL=developer.js.map
|