@dayby/mcp-server 0.3.1 → 0.3.2

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 CHANGED
@@ -17,14 +17,14 @@ draft_post → sanitized locally, never touches network
17
17
 
18
18
  | Tool | What it does | Touches network? |
19
19
  |---|---|---|
20
- | `draft_post` | Creates a sanitized draft from your description | No |
21
- | `edit_draft` | Modify a draft before publishing | No |
22
- | `check_content` | Dry-run: see what would get stripped | No |
23
- | `publish_post` | Publish an approved draft to DayBy | Yes (sanitized only) |
24
- | `list_posts` | List your recent DayBy posts | Yes |
25
- | `get_post` | Fetch a single post by slug | Yes |
26
- | `update_post` | Update title, content, or visibility | Yes |
27
- | `delete_post` | Permanently delete a post | Yes |
20
+ | `draft_post` | Creates a sanitized draft from your description | No |
21
+ | `edit_draft` | Modify a draft before publishing | No |
22
+ | `check_content` | Dry-run: see what would get stripped | No |
23
+ | `publish_post` | Publish an approved draft to DayBy | Yes (sanitized only) |
24
+ | `list_posts` | List your recent DayBy posts | Yes |
25
+ | `get_post` | Fetch a single post by slug | Yes |
26
+ | `update_post` | Update title, content, or visibility | Yes |
27
+ | `delete_post` | Permanently delete a post | Yes |
28
28
 
29
29
  ## What Gets Stripped (Automatically)
30
30
 
@@ -35,30 +35,11 @@ draft_post → sanitized locally, never touches network
35
35
  - SSH keys, JWTs, GitHub tokens
36
36
  - Database connection URLs
37
37
  - File paths with usernames
38
- - Plus anything you configure in blocklist
38
+ - Plus anything you configure in blocklist
39
39
 
40
40
  ## Setup
41
41
 
42
- ### 1. Get a DayBy API Key
43
-
44
- 1. Sign up at [dayby.dev](https://dayby.dev)
45
- 2. Go to Settings → API
46
- 3. Enable API access and generate a key
47
-
48
- ### 2. Configure Sanitizer (Optional but Recommended)
49
-
50
- Create `~/.dayby/sanitizer.json`:
51
-
52
- ```json
53
- {
54
- "blockedTerms": ["YourCompany", "ProjectCodename"],
55
- "blockedDomains": ["internal.yourcompany.com"],
56
- "blockedNames": ["Your Boss Name"],
57
- "customPatterns": ["JIRA-\\d+", "INTERNAL-\\d+"]
58
- }
59
- ```
60
-
61
- ### 3. Install
42
+ ### 1. Install
62
43
 
63
44
  **Option A — npx (no install needed):**
64
45
 
@@ -80,7 +61,25 @@ cd dayby-mcp-server
80
61
  npm install && npm run build
81
62
  ```
82
63
 
83
- ### 4. Add to Claude Code / Claude Desktop / Cursor
64
+ ### 2. Authenticate
65
+
66
+ Run the auth command to connect your DayBy account:
67
+
68
+ ```bash
69
+ dayby-mcp auth
70
+ ```
71
+
72
+ This opens your browser for a one-click authorization. Your token is saved locally at `~/.dayby/credentials.json`.
73
+
74
+ To log out:
75
+
76
+ ```bash
77
+ dayby-mcp auth --logout
78
+ ```
79
+
80
+ Alternatively, you can set the `DAYBY_API_KEY` environment variable (from Settings > API on dayby.dev).
81
+
82
+ ### 3. Add to your MCP client
84
83
 
85
84
  **Claude Code (simplest):**
86
85
 
@@ -101,10 +100,7 @@ claude mcp add dayby -- npx @dayby/mcp-server
101
100
  "mcpServers": {
102
101
  "dayby": {
103
102
  "command": "npx",
104
- "args": ["@dayby/mcp-server"],
105
- "env": {
106
- "DAYBY_API_KEY": "your-api-key-here"
107
- }
103
+ "args": ["@dayby/mcp-server"]
108
104
  }
109
105
  }
110
106
  }
@@ -117,15 +113,25 @@ claude mcp add dayby -- npx @dayby/mcp-server
117
113
  "mcpServers": {
118
114
  "dayby": {
119
115
  "command": "npx",
120
- "args": ["@dayby/mcp-server"],
121
- "env": {
122
- "DAYBY_API_KEY": "your-api-key-here"
123
- }
116
+ "args": ["@dayby/mcp-server"]
124
117
  }
125
118
  }
126
119
  }
127
120
  ```
128
121
 
122
+ ### 4. Configure Sanitizer (Optional but Recommended)
123
+
124
+ Create `~/.dayby/sanitizer.json`:
125
+
126
+ ```json
127
+ {
128
+ "blockedTerms": ["YourCompany", "ProjectCodename"],
129
+ "blockedDomains": ["internal.yourcompany.com"],
130
+ "blockedNames": ["Your Boss Name"],
131
+ "customPatterns": ["JIRA-\\d+", "INTERNAL-\\d+"]
132
+ }
133
+ ```
134
+
129
135
  ## Usage Examples
130
136
 
131
137
  **While coding:**
@@ -143,7 +149,7 @@ Claude will use `draft_post` to sanitize locally, show you a preview, and only p
143
149
 
144
150
  | Variable | Description | Default |
145
151
  |---|---|---|
146
- | `DAYBY_API_KEY` | Your DayBy API key | (required) |
152
+ | `DAYBY_API_KEY` | Your DayBy API key (alternative to `dayby-mcp auth`) | (none) |
147
153
  | `DAYBY_API_URL` | DayBy API URL | `https://dayby.dev` |
148
154
  | `DAYBY_BLOCKED_TERMS` | Comma-separated blocked terms | (none) |
149
155
  | `DAYBY_BLOCKED_DOMAINS` | Comma-separated blocked domains | (none) |
@@ -170,6 +176,10 @@ Then restart your terminal or run `source ~/.bashrc` again.
170
176
 
171
177
  Restart Claude Code / Claude Desktop after adding the MCP config.
172
178
 
179
+ **`Not authenticated` errors**
180
+
181
+ Run `dayby-mcp auth` to connect your account, or set `DAYBY_API_KEY` in your environment.
182
+
173
183
  ## License
174
184
 
175
185
  MIT
package/dist/auth.js CHANGED
@@ -45,11 +45,12 @@ exports.clearCredentials = clearCredentials;
45
45
  exports.getStoredToken = getStoredToken;
46
46
  exports.runAuthFlow = runAuthFlow;
47
47
  const fs = __importStar(require("fs"));
48
+ const os = __importStar(require("os"));
48
49
  const path = __importStar(require("path"));
49
50
  const child_process_1 = require("child_process");
50
51
  // --- Config ---
51
52
  const DEFAULT_API_URL = 'https://dayby.dev';
52
- const CREDENTIALS_DIR = path.join(process.env.HOME || '~', '.dayby');
53
+ const CREDENTIALS_DIR = path.join(os.homedir(), '.dayby');
53
54
  const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
54
55
  const POLL_INTERVAL_MS = 2000;
55
56
  // --- Storage ---
@@ -42,6 +42,7 @@ export declare class DayByClient {
42
42
  title: string;
43
43
  content: string;
44
44
  visibility?: string;
45
+ tags?: string[];
45
46
  }): Promise<{
46
47
  post: DayByPost;
47
48
  }>;
@@ -49,9 +50,14 @@ export declare class DayByClient {
49
50
  title?: string;
50
51
  content?: string;
51
52
  visibility?: string;
53
+ tags?: string[];
52
54
  }): Promise<{
53
55
  post: DayByPost;
54
56
  }>;
57
+ updateArticle(slug: string, htmlArticle: string): Promise<{
58
+ post: DayByPost;
59
+ message: string;
60
+ }>;
55
61
  deletePost(slug: string): Promise<{
56
62
  message: string;
57
63
  }>;
@@ -53,6 +53,9 @@ class DayByClient {
53
53
  post: params,
54
54
  });
55
55
  }
56
+ async updateArticle(slug, htmlArticle) {
57
+ return this.request('PUT', `/api/v2/posts/${slug}/article`, { html_article: htmlArticle });
58
+ }
56
59
  async deletePost(slug) {
57
60
  return this.request('DELETE', `/api/v2/posts/${slug}`);
58
61
  }
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ const dayby_client_js_1 = require("./dayby-client.js");
54
54
  const auth_js_1 = require("./auth.js");
55
55
  const visibility_js_1 = require("./visibility.js");
56
56
  const fs = __importStar(require("fs"));
57
+ const os = __importStar(require("os"));
57
58
  const path = __importStar(require("path"));
58
59
  // --- Auth subcommand ---
59
60
  async function handleAuthCommand() {
@@ -76,7 +77,7 @@ function loadConfig() {
76
77
  // 3. Load sanitizer config from file if it exists
77
78
  let sanitizerConfig = {};
78
79
  const configPaths = [
79
- path.join(process.env.HOME || '~', '.dayby', 'sanitizer.json'),
80
+ path.join(os.homedir(), '.dayby', 'sanitizer.json'),
80
81
  path.join(process.cwd(), '.dayby-sanitizer.json'),
81
82
  ];
82
83
  for (const configPath of configPaths) {
@@ -137,7 +138,8 @@ async function main() {
137
138
  title: zod_1.z.string().describe('Post title — focus on the technology/skill learned'),
138
139
  content: zod_1.z.string().describe('Post content — describe what you learned, built, or solved. The sanitizer will strip any sensitive data automatically.'),
139
140
  visibility: zod_1.z.enum(['published', 'draft']).default('published').describe('Post visibility on DayBy'),
140
- }, async ({ title, content, visibility }) => {
141
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Tags/project names for this post (e.g., ["playflow", "rust"]). Used to filter posts by project in the public API.'),
142
+ }, async ({ title, content, visibility, tags }) => {
141
143
  // Sanitize both title and content locally
142
144
  const titleResult = sanitizer.sanitize(title);
143
145
  const contentResult = sanitizer.sanitize(content);
@@ -150,6 +152,7 @@ async function main() {
150
152
  sanitizedContent: contentResult.clean,
151
153
  strippedItems: allStripped,
152
154
  visibility,
155
+ tags: tags || [],
153
156
  createdAt: new Date(),
154
157
  };
155
158
  drafts.set(draftId, draft);
@@ -158,6 +161,9 @@ async function main() {
158
161
  response += `**Title:** ${draft.sanitizedTitle}\n\n`;
159
162
  response += `**Content:**\n${draft.sanitizedContent}\n\n`;
160
163
  response += `**Visibility:** ${visibility}\n`;
164
+ if (draft.tags.length > 0) {
165
+ response += `**Tags:** ${draft.tags.join(', ')}\n`;
166
+ }
161
167
  if (allStripped.length > 0) {
162
168
  response += `\n⚠️ **Sanitizer removed ${allStripped.length} sensitive item(s):**\n`;
163
169
  for (const item of allStripped.slice(0, 10)) {
@@ -183,7 +189,8 @@ async function main() {
183
189
  title: zod_1.z.string().optional().describe('Updated title (will be re-sanitized)'),
184
190
  content: zod_1.z.string().optional().describe('Updated content (will be re-sanitized)'),
185
191
  visibility: zod_1.z.enum(['published', 'draft']).optional().describe('Updated visibility'),
186
- }, async ({ draft_id, title, content, visibility }) => {
192
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Updated tags/project names'),
193
+ }, async ({ draft_id, title, content, visibility, tags }) => {
187
194
  const draft = drafts.get(draft_id);
188
195
  if (!draft) {
189
196
  return {
@@ -204,6 +211,9 @@ async function main() {
204
211
  if (visibility) {
205
212
  draft.visibility = visibility;
206
213
  }
214
+ if (tags) {
215
+ draft.tags = tags;
216
+ }
207
217
  let response = `✏️ **Draft Updated** (ID: ${draft_id})\n\n`;
208
218
  response += `**Title:** ${draft.sanitizedTitle}\n\n`;
209
219
  response += `**Content:**\n${draft.sanitizedContent}\n\n`;
@@ -240,6 +250,7 @@ async function main() {
240
250
  title: draft.sanitizedTitle,
241
251
  content: draft.sanitizedContent,
242
252
  visibility: (0, visibility_js_1.toApiVisibility)(draft.visibility),
253
+ tags: draft.tags.length > 0 ? draft.tags : undefined,
243
254
  });
244
255
  let response = `✅ **Published to DayBy!**\n\n`;
245
256
  response += `**Title:** ${result.post.title}\n`;
@@ -332,7 +343,8 @@ async function main() {
332
343
  title: zod_1.z.string().optional().describe('New title (will be sanitized)'),
333
344
  content: zod_1.z.string().optional().describe('New content (will be sanitized)'),
334
345
  visibility: zod_1.z.enum(['published', 'draft']).optional().describe('New visibility'),
335
- }, async ({ slug, title, content, visibility }) => {
346
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Updated tags/project names'),
347
+ }, async ({ slug, title, content, visibility, tags }) => {
336
348
  if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
337
349
  return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
338
350
  }
@@ -343,8 +355,10 @@ async function main() {
343
355
  params.content = sanitizer.sanitize(content).clean;
344
356
  if (visibility)
345
357
  params.visibility = (0, visibility_js_1.toApiVisibility)(visibility);
358
+ if (tags)
359
+ params.tags = tags;
346
360
  if (Object.keys(params).length === 0) {
347
- return { content: [{ type: 'text', text: '❌ Provide at least one field to update (title, content, or visibility).' }] };
361
+ return { content: [{ type: 'text', text: '❌ Provide at least one field to update (title, content, visibility, or tags).' }] };
348
362
  }
349
363
  try {
350
364
  const result = await client.updatePost(slug, params);
@@ -362,6 +376,45 @@ async function main() {
362
376
  }
363
377
  });
364
378
  // ========================================
379
+ // Tool: update_article
380
+ // Let the user's AI generate or edit the HTML article directly.
381
+ // ========================================
382
+ server.tool('update_article', `Set or replace the HTML article for a DayBy post. Use this to write custom articles with CTAs, rich formatting, or any content the user wants.
383
+
384
+ DayBy article HTML rules:
385
+ - Return clean HTML fragments only (no wrapper divs, article tags, doctype, html, head, body)
386
+ - Use <p> tags for paragraphs (no classes needed)
387
+ - Use <h2> for section headings, <h3> for sub-sections
388
+ - Use <strong> for key terms, <em> for emphasis
389
+ - Use <blockquote> for pull quotes
390
+ - Use <pre><code> for code blocks, <code> inline for references
391
+ - Use <ul>/<ol> for lists (prefer prose over lists)
392
+ - Use <a href="..." target="_blank"> for links and CTAs
393
+ - No CSS classes on elements (the page stylesheet handles typography)
394
+ - Target length: 600-1000 words
395
+ - Keep the author's voice and perspective from the original post`, {
396
+ slug: zod_1.z.string().describe('The post slug to update'),
397
+ html_article: zod_1.z.string().describe('The HTML article content. Follow DayBy article HTML rules in the tool description.'),
398
+ }, async ({ slug, html_article }) => {
399
+ if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
400
+ return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
401
+ }
402
+ try {
403
+ const result = await client.updateArticle(slug, html_article);
404
+ const post = result.post;
405
+ let response = `✅ **Article Updated**\n\n`;
406
+ response += `**Title:** ${post.title}\n`;
407
+ response += `**URL:** ${post.url}\n`;
408
+ response += `${result.message}\n`;
409
+ return { content: [{ type: 'text', text: response }] };
410
+ }
411
+ catch (e) {
412
+ return {
413
+ content: [{ type: 'text', text: `❌ Failed to update article: ${e instanceof Error ? e.message : 'Unknown error'}` }],
414
+ };
415
+ }
416
+ });
417
+ // ========================================
365
418
  // Tool: delete_post
366
419
  // ========================================
367
420
  server.tool('delete_post', 'Permanently delete a DayBy post by its slug.', {
@@ -6,8 +6,9 @@ const sanitizer_js_1 = require("./sanitizer.js");
6
6
  const sanitizer = new sanitizer_js_1.Sanitizer();
7
7
  (0, vitest_1.describe)('default patterns', () => {
8
8
  (0, vitest_1.it)('strips API keys', () => {
9
- const result = sanitizer.sanitize('My api_key=sk_live_abc123def456ghi789jkl012');
10
- (0, vitest_1.expect)(result.clean).not.toContain('sk_live_abc123def456ghi789jkl012');
9
+ const fakeKey = 'xk_test_' + 'a1b2c3d4e5f6g7h8i9j0k1l2';
10
+ const result = sanitizer.sanitize(`My api_key=${fakeKey}`);
11
+ (0, vitest_1.expect)(result.clean).not.toContain(fakeKey);
11
12
  (0, vitest_1.expect)(result.stripped.length).toBeGreaterThan(0);
12
13
  });
13
14
  (0, vitest_1.it)('strips AWS access keys', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayby/mcp-server",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "DayBy MCP Server — Post your dev progress from Claude, Cursor, or any MCP client. Local sanitization built in.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {