@euqns/nudge-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/README.md +153 -0
- package/dist/credentials.js +44 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.js +164 -0
- package/dist/setup.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Nudge MCP
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes
|
|
4
|
+
the Nudge board (this repo's Trello-like app) to MCP clients — Claude Desktop,
|
|
5
|
+
Claude Code, Cursor, anything that speaks MCP over stdio.
|
|
6
|
+
|
|
7
|
+
It lets an LLM list boards, create cards, assign people, move tasks between
|
|
8
|
+
columns, drop comments, and read your notifications — using your identity, so
|
|
9
|
+
mentions and activity logs attribute correctly.
|
|
10
|
+
|
|
11
|
+
## How it authenticates
|
|
12
|
+
|
|
13
|
+
Nudge's auth is Google-only with an `@joygame.com` domain restriction, and
|
|
14
|
+
the JWTs are signed by the Convex deployment itself — there's no key the MCP
|
|
15
|
+
server could re-sign with from a CLI. So the MCP uses **personal access
|
|
16
|
+
tokens**, bootstrapped over a one-shot localhost handshake:
|
|
17
|
+
|
|
18
|
+
1. Run `npx -y @euqns/nudge-mcp setup` in a terminal.
|
|
19
|
+
2. The CLI opens your browser to Nudge's `/mcp-setup` page (you'll sign in
|
|
20
|
+
with Google if you aren't already).
|
|
21
|
+
3. Click **Authorize**. The page generates a token in your name and sends it
|
|
22
|
+
to the CLI over `http://127.0.0.1:<random-port>/callback`. The token is
|
|
23
|
+
never displayed, never typed, never logged.
|
|
24
|
+
4. The CLI writes `~/.nudge/credentials` at mode 0600 and prints the snippet
|
|
25
|
+
you paste into your MCP client — that snippet contains **no secrets**.
|
|
26
|
+
|
|
27
|
+
Replay protection is via a fresh per-run state nonce; mismatched POSTs are
|
|
28
|
+
rejected with 403. The listener only binds to `127.0.0.1` and times out
|
|
29
|
+
after 5 minutes.
|
|
30
|
+
|
|
31
|
+
You can manage tokens (name, last-used, revoke) from **profile → MCP
|
|
32
|
+
tokens** in the web app.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
For end users:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y @euqns/nudge-mcp setup
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's the whole install. No clone, no `npm run build`, no JSON editing
|
|
43
|
+
beyond the snippet the CLI prints.
|
|
44
|
+
|
|
45
|
+
For local development of this repo:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd mcp
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
node dist/index.js setup # uses the default app URL
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configure
|
|
55
|
+
|
|
56
|
+
After `setup` succeeds, copy this into your MCP client. The token lives in
|
|
57
|
+
`~/.nudge/credentials`, so the client config is the same on every machine
|
|
58
|
+
and contains no secrets:
|
|
59
|
+
|
|
60
|
+
### Claude Desktop
|
|
61
|
+
|
|
62
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"nudge": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@euqns/nudge-mcp"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Restart Claude Desktop. The Nudge tools appear under the tools menu.
|
|
76
|
+
|
|
77
|
+
### Claude Code
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
claude mcp add nudge npx -y @euqns/nudge-mcp
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Cursor
|
|
84
|
+
|
|
85
|
+
`.cursor/mcp.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"nudge": {
|
|
91
|
+
"command": "npx",
|
|
92
|
+
"args": ["-y", "@euqns/nudge-mcp"]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Overrides
|
|
99
|
+
|
|
100
|
+
If you need to point at a non-production deployment (PR preview, local dev
|
|
101
|
+
against `convex dev`), `setup` accepts `--app-url`:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx -y @euqns/nudge-mcp setup --app-url http://localhost:5173
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
You can also bypass the credentials file entirely by setting
|
|
108
|
+
`NUDGE_CONVEX_URL` and `NUDGE_TOKEN` in the MCP client's `env` block —
|
|
109
|
+
useful for CI or containers.
|
|
110
|
+
|
|
111
|
+
## Tools
|
|
112
|
+
|
|
113
|
+
| Tool | What it does |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `whoami` | Identity bound to the token (id, email, displayName, team). |
|
|
116
|
+
| `list_boards` | Every board the user is a member of. |
|
|
117
|
+
| `get_board` | Board layout: lists, labels, members, your role. |
|
|
118
|
+
| `create_board` | New board (caller becomes owner, default Backlog/In progress/Review/Done columns). |
|
|
119
|
+
| `list_lists` / `create_list` | Read or add columns. |
|
|
120
|
+
| `list_cards` | All cards on a board (subtasks hidden by default). |
|
|
121
|
+
| `my_cards` | All cards across all boards where the caller is assigned, soonest due first. |
|
|
122
|
+
| `get_card` | One card incl. its description doc. |
|
|
123
|
+
| `create_card` | New card in a list (pass `parentCardId` for subtasks). |
|
|
124
|
+
| `update_card` | Patch title/icon/dueDate/assigneeIds/labelIds/priority/listId/description. Pass `'none'` to clear `dueDate`/`priority`. |
|
|
125
|
+
| `move_card` | Drop into another list. Auto-completes if landing in a Done list. |
|
|
126
|
+
| `set_card_completed` | Flip the complete bit explicitly. |
|
|
127
|
+
| `delete_card` | Hard-delete with cascade (subtasks, comments, activity, notifications). |
|
|
128
|
+
| `create_label` | Board-scoped label. |
|
|
129
|
+
| `list_comments` / `add_comment` | Comment threads on a card. `@username` mentions notify. |
|
|
130
|
+
| `list_members` | Board members (use the returned ids for `assigneeIds`). |
|
|
131
|
+
| `find_user_by_email` | Resolve an email to a user id — handy before assigning. |
|
|
132
|
+
| `my_notifications` | Recent notifications (assigned / mentioned / commented / moved / updated). |
|
|
133
|
+
|
|
134
|
+
## Boundary notes
|
|
135
|
+
|
|
136
|
+
- The server runs every call as the user the token belongs to — board access
|
|
137
|
+
is checked against `memberships`, the same table the web app reads. There
|
|
138
|
+
is no "elevated" mode.
|
|
139
|
+
- Convex actions run on the same backend as the app, so MCP-driven changes
|
|
140
|
+
appear in real time in any open browser tab.
|
|
141
|
+
- The token is sent as a regular argument over the wire (HTTPS to Convex).
|
|
142
|
+
Anyone reading the JSON payload would see it, which is why we hash on the
|
|
143
|
+
server and only ever return the plaintext at creation time.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm run dev # tsx watcher — useful with `node --inspect` on the MCP host
|
|
149
|
+
npm run build # tsc to dist/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The server doesn't reload tools at runtime; restart the MCP client when you
|
|
153
|
+
change tool definitions in `src/index.ts`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Local credentials file persistence. Tokens never sit in shell env or MCP
|
|
2
|
+
// client JSON — they're held in `~/.nudge/credentials` (mode 0600) so only
|
|
3
|
+
// the user can read them.
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
export const CREDENTIALS_DIR = path.join(os.homedir(), ".nudge");
|
|
8
|
+
export const CREDENTIALS_PATH = path.join(CREDENTIALS_DIR, "credentials");
|
|
9
|
+
export async function readCredentials() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await fs.readFile(CREDENTIALS_PATH, "utf8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (typeof parsed.token !== "string" ||
|
|
14
|
+
typeof parsed.convexUrl !== "string") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return { token: parsed.token, convexUrl: parsed.convexUrl };
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err.code === "ENOENT")
|
|
21
|
+
return null;
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function writeCredentials(creds) {
|
|
26
|
+
await fs.mkdir(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
|
|
27
|
+
// chmod the file explicitly after write — fs.writeFile's `mode` is only
|
|
28
|
+
// applied on file creation, so a pre-existing wider-mode file would keep
|
|
29
|
+
// its old perms otherwise.
|
|
30
|
+
await fs.writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), {
|
|
31
|
+
mode: 0o600,
|
|
32
|
+
});
|
|
33
|
+
await fs.chmod(CREDENTIALS_PATH, 0o600);
|
|
34
|
+
}
|
|
35
|
+
export async function deleteCredentials() {
|
|
36
|
+
try {
|
|
37
|
+
await fs.unlink(CREDENTIALS_PATH);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err.code !== "ENOENT")
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,0BAA0B;AAE1B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IACE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAkB;IACvD,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,wEAAwE;IACxE,yEAAyE;IACzE,2BAA2B;IAC3B,MAAM,EAAE,CAAC,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACnE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Nudge MCP server. Exposes the Nudge board's everyday operations (boards,
|
|
3
|
+
// lists, cards, comments, members, notifications) as MCP tools backed by
|
|
4
|
+
// Convex actions.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// nudge-mcp setup # one-time browser-based auth — writes ~/.nudge/credentials
|
|
8
|
+
// nudge-mcp # default: run the MCP stdio server using stored credentials
|
|
9
|
+
//
|
|
10
|
+
// Override the stored credentials with NUDGE_CONVEX_URL / NUDGE_TOKEN env
|
|
11
|
+
// vars if you need to (CI, container, debugging).
|
|
12
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
16
|
+
import { makeFunctionReference } from "convex/server";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { CREDENTIALS_PATH, readCredentials } from "./credentials.js";
|
|
19
|
+
import { runSetup } from "./setup.js";
|
|
20
|
+
// ---- entrypoint dispatch --------------------------------------------------
|
|
21
|
+
const subcommand = process.argv[2];
|
|
22
|
+
if (subcommand === "setup") {
|
|
23
|
+
try {
|
|
24
|
+
await runSetup(process.argv.slice(3));
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error("[nudge-mcp setup] failed:", err instanceof Error ? err.message : err);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
33
|
+
console.log([
|
|
34
|
+
"Nudge MCP server",
|
|
35
|
+
"",
|
|
36
|
+
"Commands:",
|
|
37
|
+
" nudge-mcp setup [--app-url URL] Run the browser-based setup flow.",
|
|
38
|
+
" nudge-mcp Start the MCP stdio server (default).",
|
|
39
|
+
"",
|
|
40
|
+
`Credentials live at ${CREDENTIALS_PATH} (mode 0600).`,
|
|
41
|
+
"Override with NUDGE_CONVEX_URL / NUDGE_TOKEN env vars if needed.",
|
|
42
|
+
].join("\n"));
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
// ---- credentials ----------------------------------------------------------
|
|
46
|
+
const stored = await readCredentials();
|
|
47
|
+
const CONVEX_URL = process.env.NUDGE_CONVEX_URL ?? stored?.convexUrl;
|
|
48
|
+
const TOKEN = process.env.NUDGE_TOKEN ?? stored?.token;
|
|
49
|
+
if (!CONVEX_URL || !TOKEN) {
|
|
50
|
+
console.error([
|
|
51
|
+
"[nudge-mcp] No credentials found.",
|
|
52
|
+
"",
|
|
53
|
+
"Run `npx -y @euqns/nudge-mcp setup` once to authorize this machine,",
|
|
54
|
+
"or set NUDGE_CONVEX_URL and NUDGE_TOKEN in the environment.",
|
|
55
|
+
].join("\n"));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const convex = new ConvexHttpClient(CONVEX_URL);
|
|
59
|
+
// `mcp.X` is the user-facing surface in convex/mcp.ts; we reference it by name
|
|
60
|
+
// because the MCP server is published independently of the Convex codegen and
|
|
61
|
+
// shouldn't rely on the app's generated types.
|
|
62
|
+
async function action(name, args) {
|
|
63
|
+
// makeFunctionReference accepts the "module:function" form Convex uses on
|
|
64
|
+
// the wire; the generic params are loose because the MCP server doesn't
|
|
65
|
+
// pull in the app's generated types.
|
|
66
|
+
const ref = makeFunctionReference(name);
|
|
67
|
+
try {
|
|
68
|
+
return await convex.action(ref, {
|
|
69
|
+
token: TOKEN,
|
|
70
|
+
...args,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
throw new Error(`Nudge action ${name} failed: ${msg}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ---- tool definitions -----------------------------------------------------
|
|
79
|
+
// Schemas defined with zod so we get runtime validation AND can dump JSON
|
|
80
|
+
// schema for MCP's inputSchema field via zodToJsonSchema lite. We hand-roll a
|
|
81
|
+
// tiny converter because we only need basic shapes (objects of primitives /
|
|
82
|
+
// optional strings / unions).
|
|
83
|
+
const schemas = {
|
|
84
|
+
whoami: z.object({}),
|
|
85
|
+
list_boards: z.object({}),
|
|
86
|
+
get_board: z.object({
|
|
87
|
+
boardId: z.string().describe("The board id (looks like 'kg…')."),
|
|
88
|
+
}),
|
|
89
|
+
create_board: z.object({
|
|
90
|
+
title: z.string().describe("Board title."),
|
|
91
|
+
description: z.string().optional(),
|
|
92
|
+
icon: z.string().optional().describe("Emoji or short icon string."),
|
|
93
|
+
swatch: z.string().optional().describe("CSS color for the board accent."),
|
|
94
|
+
}),
|
|
95
|
+
list_lists: z.object({ boardId: z.string() }),
|
|
96
|
+
create_list: z.object({ boardId: z.string(), title: z.string() }),
|
|
97
|
+
list_cards: z.object({
|
|
98
|
+
boardId: z.string(),
|
|
99
|
+
includeSubtasks: z.boolean().optional(),
|
|
100
|
+
}),
|
|
101
|
+
my_cards: z.object({}),
|
|
102
|
+
get_card: z.object({ cardId: z.string() }),
|
|
103
|
+
create_card: z.object({
|
|
104
|
+
boardId: z.string(),
|
|
105
|
+
listId: z.string(),
|
|
106
|
+
title: z.string(),
|
|
107
|
+
parentCardId: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Pass to make this card a subtask of another card."),
|
|
111
|
+
}),
|
|
112
|
+
update_card: z.object({
|
|
113
|
+
cardId: z.string(),
|
|
114
|
+
title: z.string().optional(),
|
|
115
|
+
icon: z.string().optional(),
|
|
116
|
+
dueDate: z
|
|
117
|
+
.union([z.number(), z.literal("none")])
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Unix ms timestamp, or 'none' to clear the due date."),
|
|
120
|
+
assigneeIds: z
|
|
121
|
+
.array(z.string())
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("Replaces the current assignee list. Use list_members to resolve user ids."),
|
|
124
|
+
labelIds: z.array(z.string()).optional(),
|
|
125
|
+
priority: z
|
|
126
|
+
.enum(["urgent", "high", "medium", "low", "none"])
|
|
127
|
+
.optional(),
|
|
128
|
+
listId: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("Move the card to a different list."),
|
|
132
|
+
description: z
|
|
133
|
+
.string()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("Plain-text description. Replaces the card body; line breaks become paragraphs."),
|
|
136
|
+
}),
|
|
137
|
+
move_card: z.object({
|
|
138
|
+
cardId: z.string(),
|
|
139
|
+
toListId: z.string(),
|
|
140
|
+
newOrder: z
|
|
141
|
+
.number()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Optional sort key; omit to drop the card at the end of the target list."),
|
|
144
|
+
}),
|
|
145
|
+
set_card_completed: z.object({
|
|
146
|
+
cardId: z.string(),
|
|
147
|
+
completed: z.boolean(),
|
|
148
|
+
}),
|
|
149
|
+
delete_card: z.object({ cardId: z.string() }),
|
|
150
|
+
create_label: z.object({
|
|
151
|
+
boardId: z.string(),
|
|
152
|
+
name: z.string(),
|
|
153
|
+
color: z.string().describe("CSS color string."),
|
|
154
|
+
}),
|
|
155
|
+
list_comments: z.object({ cardId: z.string() }),
|
|
156
|
+
add_comment: z.object({
|
|
157
|
+
cardId: z.string(),
|
|
158
|
+
body: z.string(),
|
|
159
|
+
parentId: z
|
|
160
|
+
.string()
|
|
161
|
+
.optional()
|
|
162
|
+
.describe("Reply to a specific comment on the same card."),
|
|
163
|
+
}),
|
|
164
|
+
list_members: z.object({ boardId: z.string() }),
|
|
165
|
+
find_user_by_email: z.object({ email: z.string() }),
|
|
166
|
+
my_notifications: z.object({
|
|
167
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
const tools = {
|
|
171
|
+
whoami: {
|
|
172
|
+
description: "Return the Nudge user the current token belongs to (id, email, displayName, team, jobTitle).",
|
|
173
|
+
action: "mcp:whoami",
|
|
174
|
+
},
|
|
175
|
+
list_boards: {
|
|
176
|
+
description: "List boards the current user is a member of. Returns id, title, role, icon, swatch.",
|
|
177
|
+
action: "mcp:listBoards",
|
|
178
|
+
},
|
|
179
|
+
get_board: {
|
|
180
|
+
description: "Get a board's full layout: lists (in order), labels, members, and the caller's role.",
|
|
181
|
+
action: "mcp:getBoard",
|
|
182
|
+
},
|
|
183
|
+
create_board: {
|
|
184
|
+
description: "Create a new board. The caller becomes the owner and a default Backlog/In progress/Review/Done list set is seeded.",
|
|
185
|
+
action: "mcp:createBoard",
|
|
186
|
+
},
|
|
187
|
+
list_lists: {
|
|
188
|
+
description: "List columns (lists) on a board, ordered.",
|
|
189
|
+
action: "mcp:listLists",
|
|
190
|
+
},
|
|
191
|
+
create_list: {
|
|
192
|
+
description: "Add a column to a board. Appended after existing lists.",
|
|
193
|
+
action: "mcp:createList",
|
|
194
|
+
},
|
|
195
|
+
list_cards: {
|
|
196
|
+
description: "List cards on a board. Subtasks are hidden by default — pass includeSubtasks=true to include them.",
|
|
197
|
+
action: "mcp:listCards",
|
|
198
|
+
},
|
|
199
|
+
my_cards: {
|
|
200
|
+
description: "List every card across every board where the current user is assigned, soonest due first.",
|
|
201
|
+
action: "mcp:myCards",
|
|
202
|
+
},
|
|
203
|
+
get_card: {
|
|
204
|
+
description: "Get a card with its list title and Tiptap description doc.",
|
|
205
|
+
action: "mcp:getCard",
|
|
206
|
+
},
|
|
207
|
+
create_card: {
|
|
208
|
+
description: "Create a card in a list. Pass parentCardId to make it a subtask of an existing card.",
|
|
209
|
+
action: "mcp:createCard",
|
|
210
|
+
},
|
|
211
|
+
update_card: {
|
|
212
|
+
description: "Patch a card. Any field omitted is left untouched. Send dueDate='none' to clear, priority='none' to clear, listId to move.",
|
|
213
|
+
action: "mcp:updateCard",
|
|
214
|
+
},
|
|
215
|
+
move_card: {
|
|
216
|
+
description: "Move a card to another list on the same board.",
|
|
217
|
+
action: "mcp:moveCard",
|
|
218
|
+
},
|
|
219
|
+
set_card_completed: {
|
|
220
|
+
description: "Mark a card complete or reopen it. Note: dropping a card into a list flagged isDone (or titled 'Done') also auto-completes it.",
|
|
221
|
+
action: "mcp:setCardCompleted",
|
|
222
|
+
},
|
|
223
|
+
delete_card: {
|
|
224
|
+
description: "Delete a card and cascade-delete its subtasks, comments, activity, and notifications.",
|
|
225
|
+
action: "mcp:deleteCard",
|
|
226
|
+
},
|
|
227
|
+
create_label: {
|
|
228
|
+
description: "Create a board-scoped label.",
|
|
229
|
+
action: "mcp:createLabel",
|
|
230
|
+
},
|
|
231
|
+
list_comments: {
|
|
232
|
+
description: "List comments on a card, oldest first, with author email + name.",
|
|
233
|
+
action: "mcp:listComments",
|
|
234
|
+
},
|
|
235
|
+
add_comment: {
|
|
236
|
+
description: "Add a comment. Plain @username mentions notify board members; pass parentId to thread under another comment.",
|
|
237
|
+
action: "mcp:addComment",
|
|
238
|
+
},
|
|
239
|
+
list_members: {
|
|
240
|
+
description: "List members of a board (id, role, email, displayName).",
|
|
241
|
+
action: "mcp:listMembers",
|
|
242
|
+
},
|
|
243
|
+
find_user_by_email: {
|
|
244
|
+
description: "Look up a user by email — useful before assigning a card to someone via update_card.assigneeIds.",
|
|
245
|
+
action: "mcp:findUserByEmail",
|
|
246
|
+
},
|
|
247
|
+
my_notifications: {
|
|
248
|
+
description: "Recent notifications for the current user (assigned/mentioned/commented/moved/updated), newest first.",
|
|
249
|
+
action: "mcp:myNotifications",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
// Convex actions are referenced as "module:function" in HTTP calls. Translate
|
|
253
|
+
// the friendly "mcp:whoami" form into Convex's API path.
|
|
254
|
+
function actionPath(slug) {
|
|
255
|
+
const [module, fn] = slug.split(":");
|
|
256
|
+
return `${module}/${fn}`;
|
|
257
|
+
}
|
|
258
|
+
// ---- json schema conversion ----------------------------------------------
|
|
259
|
+
// Tiny zod → JSON schema for the cases the tools use. Bigger packages exist
|
|
260
|
+
// but we don't want a heavyweight dep just to describe ~20 small inputs.
|
|
261
|
+
function zodToJsonSchema(s) {
|
|
262
|
+
if (s instanceof z.ZodOptional) {
|
|
263
|
+
return zodToJsonSchema(s.unwrap());
|
|
264
|
+
}
|
|
265
|
+
if (s instanceof z.ZodObject) {
|
|
266
|
+
const shape = s.shape;
|
|
267
|
+
const properties = {};
|
|
268
|
+
const required = [];
|
|
269
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
270
|
+
properties[key] = zodToJsonSchema(child);
|
|
271
|
+
if (!(child instanceof z.ZodOptional))
|
|
272
|
+
required.push(key);
|
|
273
|
+
}
|
|
274
|
+
const out = {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties,
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
};
|
|
279
|
+
if (required.length > 0)
|
|
280
|
+
out.required = required;
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
if (s instanceof z.ZodString) {
|
|
284
|
+
return withDescription({ type: "string" }, s.description);
|
|
285
|
+
}
|
|
286
|
+
if (s instanceof z.ZodNumber) {
|
|
287
|
+
return withDescription({ type: "number" }, s.description);
|
|
288
|
+
}
|
|
289
|
+
if (s instanceof z.ZodBoolean) {
|
|
290
|
+
return withDescription({ type: "boolean" }, s.description);
|
|
291
|
+
}
|
|
292
|
+
if (s instanceof z.ZodArray) {
|
|
293
|
+
return withDescription({
|
|
294
|
+
type: "array",
|
|
295
|
+
items: zodToJsonSchema(s.element),
|
|
296
|
+
}, s.description);
|
|
297
|
+
}
|
|
298
|
+
if (s instanceof z.ZodLiteral) {
|
|
299
|
+
return { const: s.value };
|
|
300
|
+
}
|
|
301
|
+
if (s instanceof z.ZodEnum) {
|
|
302
|
+
return { type: "string", enum: s.options };
|
|
303
|
+
}
|
|
304
|
+
if (s instanceof z.ZodUnion) {
|
|
305
|
+
return {
|
|
306
|
+
anyOf: s.options.map(zodToJsonSchema),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// Fallback — accept anything. Better to be permissive than to break a tool
|
|
310
|
+
// call because our converter doesn't cover an exotic case.
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
function withDescription(obj, description) {
|
|
314
|
+
if (description)
|
|
315
|
+
return { ...obj, description };
|
|
316
|
+
return obj;
|
|
317
|
+
}
|
|
318
|
+
// ---- server wiring --------------------------------------------------------
|
|
319
|
+
const server = new Server({
|
|
320
|
+
name: "nudge-mcp",
|
|
321
|
+
version: "0.1.0",
|
|
322
|
+
}, {
|
|
323
|
+
capabilities: {
|
|
324
|
+
tools: {},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
328
|
+
const list = Object.keys(tools).map((name) => ({
|
|
329
|
+
name,
|
|
330
|
+
description: tools[name].description,
|
|
331
|
+
inputSchema: zodToJsonSchema(schemas[name]),
|
|
332
|
+
}));
|
|
333
|
+
return { tools: list };
|
|
334
|
+
});
|
|
335
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
336
|
+
const name = request.params.name;
|
|
337
|
+
if (!(name in tools)) {
|
|
338
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
339
|
+
}
|
|
340
|
+
const schema = schemas[name];
|
|
341
|
+
const parsed = schema.safeParse(request.params.arguments ?? {});
|
|
342
|
+
if (!parsed.success) {
|
|
343
|
+
throw new Error(`Invalid arguments for ${name}: ${JSON.stringify(parsed.error.flatten())}`);
|
|
344
|
+
}
|
|
345
|
+
const result = await action(actionPath(tools[name].action), parsed.data);
|
|
346
|
+
// MCP returns tool results as content arrays. Encode our JSON as a single
|
|
347
|
+
// text block — readable by the model and easy to round-trip back.
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: JSON.stringify(result, null, 2),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
const transport = new StdioServerTransport();
|
|
358
|
+
await server.connect(transport);
|
|
359
|
+
console.error("[nudge-mcp] listening on stdio");
|
|
360
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// `nudge-mcp setup` flow.
|
|
2
|
+
//
|
|
3
|
+
// Brings up a one-shot localhost listener, opens the Nudge web app's
|
|
4
|
+
// `/mcp-setup` route in the user's browser, and waits for the page to POST
|
|
5
|
+
// the freshly-minted token back. The page generates the token via the
|
|
6
|
+
// signed-in user's existing session — no copy-paste, no secret in the MCP
|
|
7
|
+
// client config.
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
|
+
import http from "node:http";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import { CREDENTIALS_PATH, writeCredentials } from "./credentials.js";
|
|
13
|
+
// Production deployment. Override with --app-url or NUDGE_APP_URL when
|
|
14
|
+
// running against a different environment (PR preview, local dev, etc.).
|
|
15
|
+
const DEFAULT_APP_URL = "https://trello-omega-nine.vercel.app";
|
|
16
|
+
const SETUP_TIMEOUT_MS = 5 * 60_000;
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
let appUrl = process.env.NUDGE_APP_URL ?? DEFAULT_APP_URL;
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
if (arg === "--app-url") {
|
|
22
|
+
const next = argv[i + 1];
|
|
23
|
+
if (!next)
|
|
24
|
+
throw new Error("--app-url requires a value");
|
|
25
|
+
appUrl = next;
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
else if (arg.startsWith("--app-url=")) {
|
|
29
|
+
appUrl = arg.slice("--app-url=".length);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { appUrl: appUrl.replace(/\/$/, "") };
|
|
33
|
+
}
|
|
34
|
+
function openBrowser(url) {
|
|
35
|
+
// Hand-rolled instead of pulling in `open` — saves a dep + keeps the binary
|
|
36
|
+
// small. Detach + ignore stdio so the child outlives the parent if the
|
|
37
|
+
// setup CLI exits early.
|
|
38
|
+
const platform = process.platform;
|
|
39
|
+
if (platform === "darwin") {
|
|
40
|
+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
41
|
+
}
|
|
42
|
+
else if (platform === "win32") {
|
|
43
|
+
// start.exe doesn't accept the URL directly without `""` as title — use
|
|
44
|
+
// cmd /c so quoting is handled correctly.
|
|
45
|
+
spawn("cmd", ["/c", "start", "", url], {
|
|
46
|
+
detached: true,
|
|
47
|
+
stdio: "ignore",
|
|
48
|
+
}).unref();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function corsHeaders(origin) {
|
|
55
|
+
// The localhost listener lives for one auth handshake. Allow any origin —
|
|
56
|
+
// the `state` nonce in the POST body is what protects us, not Origin.
|
|
57
|
+
return {
|
|
58
|
+
"Access-Control-Allow-Origin": origin ?? "*",
|
|
59
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
60
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
61
|
+
"Access-Control-Max-Age": "86400",
|
|
62
|
+
Vary: "Origin",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function readBody(req) {
|
|
66
|
+
const chunks = [];
|
|
67
|
+
for await (const c of req)
|
|
68
|
+
chunks.push(c);
|
|
69
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
70
|
+
}
|
|
71
|
+
export async function runSetup(argv) {
|
|
72
|
+
const { appUrl } = parseArgs(argv);
|
|
73
|
+
const state = crypto.randomBytes(24).toString("base64url");
|
|
74
|
+
const hostname = os.hostname();
|
|
75
|
+
const result = await new Promise((resolve, reject) => {
|
|
76
|
+
const timeout = setTimeout(() => {
|
|
77
|
+
reject(new Error(`Timed out after ${SETUP_TIMEOUT_MS / 1000}s`));
|
|
78
|
+
}, SETUP_TIMEOUT_MS);
|
|
79
|
+
const server = http.createServer(async (req, res) => {
|
|
80
|
+
const origin = req.headers.origin;
|
|
81
|
+
if (req.method === "OPTIONS") {
|
|
82
|
+
res.writeHead(204, corsHeaders(origin));
|
|
83
|
+
res.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (req.method !== "POST" || req.url !== "/callback") {
|
|
87
|
+
res.writeHead(404, corsHeaders(origin));
|
|
88
|
+
res.end();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const body = await readBody(req);
|
|
93
|
+
const parsed = JSON.parse(body);
|
|
94
|
+
if (typeof parsed.token !== "string" ||
|
|
95
|
+
typeof parsed.convexUrl !== "string" ||
|
|
96
|
+
typeof parsed.state !== "string") {
|
|
97
|
+
res.writeHead(400, corsHeaders(origin));
|
|
98
|
+
res.end(JSON.stringify({ ok: false, error: "missing fields" }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (parsed.state !== state) {
|
|
102
|
+
// CSRF/replay protection — only accept the state the CLI handed to
|
|
103
|
+
// the browser when it opened the URL.
|
|
104
|
+
res.writeHead(403, corsHeaders(origin));
|
|
105
|
+
res.end(JSON.stringify({ ok: false, error: "state mismatch" }));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
res.writeHead(200, {
|
|
109
|
+
...corsHeaders(origin),
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
});
|
|
112
|
+
res.end(JSON.stringify({ ok: true }));
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
resolve({
|
|
115
|
+
token: parsed.token,
|
|
116
|
+
convexUrl: parsed.convexUrl,
|
|
117
|
+
state: parsed.state,
|
|
118
|
+
});
|
|
119
|
+
// Brief delay so the response has a chance to flush before close.
|
|
120
|
+
setTimeout(() => server.close(), 50);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
res.writeHead(500, corsHeaders(origin));
|
|
124
|
+
res.end(JSON.stringify({ ok: false, error: String(err) }));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
server.on("error", (err) => {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
131
|
+
server.listen(0, "127.0.0.1", () => {
|
|
132
|
+
const port = server.address().port;
|
|
133
|
+
const url = new URL("/mcp-setup", appUrl);
|
|
134
|
+
url.searchParams.set("port", String(port));
|
|
135
|
+
url.searchParams.set("state", state);
|
|
136
|
+
url.searchParams.set("hostname", hostname);
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log("Opening Nudge in your browser to authorize this install…");
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(" If the browser doesn't open, visit:");
|
|
141
|
+
console.log(` ${url.toString()}`);
|
|
142
|
+
console.log("");
|
|
143
|
+
openBrowser(url.toString());
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
await writeCredentials({
|
|
147
|
+
token: result.token,
|
|
148
|
+
convexUrl: result.convexUrl,
|
|
149
|
+
});
|
|
150
|
+
console.log(`✓ Token saved to ${CREDENTIALS_PATH} (mode 0600)`);
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log("Add this to your MCP client config — no secrets needed:");
|
|
153
|
+
console.log("");
|
|
154
|
+
console.log(JSON.stringify({
|
|
155
|
+
mcpServers: {
|
|
156
|
+
nudge: {
|
|
157
|
+
command: "npx",
|
|
158
|
+
args: ["-y", "@euqns/nudge-mcp"],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}, null, 2));
|
|
162
|
+
console.log("");
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@euqns/nudge-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Model Context Protocol server for the Nudge board (the in-house Trello).",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nudge-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p .",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
27
|
+
"convex": "^1.39.1",
|
|
28
|
+
"zod": "^3.23.8"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"tsx": "^4.19.0",
|
|
33
|
+
"typescript": "^5.6.3"
|
|
34
|
+
}
|
|
35
|
+
}
|