@dayby/mcp-server 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,11 +5,10 @@ Post your dev progress to [DayBy.dev](https://dayby.dev) from Claude, Cursor, or
5
5
  ## How It Works
6
6
 
7
7
  ```
8
- You're coding with Claude learn something cool →
9
- draft_post (sanitized locally, never touches network)
10
- Claude shows you a clean preview →
11
- You say "ship it"
12
- publish_post → DayBy API (only sanitized content sent)
8
+ draft_post sanitized locally, never touches network
9
+ Claude shows you a clean preview
10
+ you approve
11
+ publish_post DayBy API (sanitized content only)
13
12
  ```
14
13
 
15
14
  **The raw context from your codebase never touches the network.** Only the sanitized, approved version gets published.
@@ -23,6 +22,9 @@ You're coding with Claude → learn something cool →
23
22
  | `check_content` | Dry-run: see what would get stripped | ❌ No |
24
23
  | `publish_post` | Publish an approved draft to DayBy | ✅ Yes (sanitized only) |
25
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 |
26
28
 
27
29
  ## What Gets Stripped (Automatically)
28
30
 
@@ -58,23 +60,48 @@ Create `~/.dayby/sanitizer.json`:
58
60
 
59
61
  ### 3. Install
60
62
 
63
+ **Option A — npx (no install needed):**
64
+
65
+ ```bash
66
+ npx @dayby/mcp-server
67
+ ```
68
+
69
+ **Option B — global install:**
70
+
61
71
  ```bash
62
72
  npm install -g @dayby/mcp-server
63
73
  ```
64
74
 
75
+ **Option C — from source:**
76
+
77
+ ```bash
78
+ git clone https://github.com/ja-roque/dayby-mcp-server.git
79
+ cd dayby-mcp-server
80
+ npm install && npm run build
81
+ ```
82
+
65
83
  ### 4. Add to Claude Code / Claude Desktop / Cursor
66
84
 
67
85
  **Claude Code (simplest):**
86
+
68
87
  ```bash
69
88
  claude mcp add dayby -- dayby-mcp
70
89
  ```
71
90
 
91
+ Or with npx:
92
+
93
+ ```bash
94
+ claude mcp add dayby -- npx @dayby/mcp-server
95
+ ```
96
+
72
97
  **Claude Desktop** (`claude_desktop_config.json`):
98
+
73
99
  ```json
74
100
  {
75
101
  "mcpServers": {
76
102
  "dayby": {
77
- "command": "dayby-mcp",
103
+ "command": "npx",
104
+ "args": ["@dayby/mcp-server"],
78
105
  "env": {
79
106
  "DAYBY_API_KEY": "your-api-key-here"
80
107
  }
@@ -84,11 +111,13 @@ claude mcp add dayby -- dayby-mcp
84
111
  ```
85
112
 
86
113
  **Cursor** (`.cursor/mcp.json`):
114
+
87
115
  ```json
88
116
  {
89
117
  "mcpServers": {
90
118
  "dayby": {
91
- "command": "dayby-mcp",
119
+ "command": "npx",
120
+ "args": ["@dayby/mcp-server"],
92
121
  "env": {
93
122
  "DAYBY_API_KEY": "your-api-key-here"
94
123
  }
@@ -119,6 +148,28 @@ Claude will use `draft_post` to sanitize locally, show you a preview, and only p
119
148
  | `DAYBY_BLOCKED_TERMS` | Comma-separated blocked terms | (none) |
120
149
  | `DAYBY_BLOCKED_DOMAINS` | Comma-separated blocked domains | (none) |
121
150
 
151
+ ## Troubleshooting
152
+
153
+ **`dayby-mcp: command not found` after global install**
154
+
155
+ Your npm global bin isn't in your PATH. Run:
156
+
157
+ ```bash
158
+ source ~/.bashrc
159
+ ```
160
+
161
+ Or add this to your `~/.bashrc` / `~/.zshrc`:
162
+
163
+ ```bash
164
+ export PATH="$(npm bin -g):$PATH"
165
+ ```
166
+
167
+ Then restart your terminal or run `source ~/.bashrc` again.
168
+
169
+ **MCP server not showing up in Claude**
170
+
171
+ Restart Claude Code / Claude Desktop after adding the MCP config.
172
+
122
173
  ## License
123
174
 
124
175
  MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * DayBy MCP — device authorization flow.
3
+ *
4
+ * Usage:
5
+ * dayby-mcp auth → interactive OAuth-style flow, saves token
6
+ * dayby-mcp auth --logout → clears saved credentials
7
+ */
8
+ interface Credentials {
9
+ token: string;
10
+ api_url: string;
11
+ }
12
+ export declare function loadCredentials(): Credentials | null;
13
+ export declare function clearCredentials(): void;
14
+ export declare function getStoredToken(apiUrl?: string): string | null;
15
+ export declare function runAuthFlow(apiUrl?: string): Promise<void>;
16
+ export {};
package/dist/auth.js ADDED
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * DayBy MCP — device authorization flow.
4
+ *
5
+ * Usage:
6
+ * dayby-mcp auth → interactive OAuth-style flow, saves token
7
+ * dayby-mcp auth --logout → clears saved credentials
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.loadCredentials = loadCredentials;
44
+ exports.clearCredentials = clearCredentials;
45
+ exports.getStoredToken = getStoredToken;
46
+ exports.runAuthFlow = runAuthFlow;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const child_process_1 = require("child_process");
50
+ // --- Config ---
51
+ const DEFAULT_API_URL = 'https://dayby.dev';
52
+ const CREDENTIALS_DIR = path.join(process.env.HOME || '~', '.dayby');
53
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
54
+ const POLL_INTERVAL_MS = 2000;
55
+ // --- Storage ---
56
+ function loadCredentials() {
57
+ try {
58
+ return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf-8'));
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function saveCredentials(creds) {
65
+ fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
66
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
67
+ }
68
+ function clearCredentials() {
69
+ try {
70
+ fs.unlinkSync(CREDENTIALS_FILE);
71
+ console.log('Logged out. Credentials cleared.');
72
+ }
73
+ catch {
74
+ console.log('No credentials found.');
75
+ }
76
+ }
77
+ function getStoredToken(apiUrl = DEFAULT_API_URL) {
78
+ const creds = loadCredentials();
79
+ if (!creds)
80
+ return null;
81
+ // If the stored token is for a different server, ignore it
82
+ if (creds.api_url !== apiUrl)
83
+ return null;
84
+ return creds.token;
85
+ }
86
+ // --- Browser ---
87
+ function openBrowser(url) {
88
+ const display = process.env.DISPLAY || ':0';
89
+ const cmd = process.platform === 'darwin' ? `open "${url}"` :
90
+ process.platform === 'win32' ? `start "" "${url}"` :
91
+ `DISPLAY=${display} xdg-open "${url}"`;
92
+ (0, child_process_1.exec)(cmd, (err) => {
93
+ if (err) {
94
+ console.error(`Could not open browser: ${err.message}`);
95
+ console.log(`Please open this URL manually:\n ${url}`);
96
+ }
97
+ });
98
+ }
99
+ // --- Poll ---
100
+ async function pollForToken(apiUrl, code) {
101
+ const url = `${apiUrl}/api/v2/auth/device/${code}`;
102
+ for (;;) {
103
+ await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
104
+ const res = await fetch(url);
105
+ if (res.status === 200) {
106
+ const data = await res.json();
107
+ return data.token;
108
+ }
109
+ if (res.status === 410) {
110
+ throw new Error('Authorization expired. Please run `dayby-mcp auth` again.');
111
+ }
112
+ if (res.status !== 202) {
113
+ throw new Error(`Unexpected response while waiting for authorization (${res.status}).`);
114
+ }
115
+ // 202 = still pending, keep polling
116
+ }
117
+ }
118
+ // --- Main flow ---
119
+ async function runAuthFlow(apiUrl = DEFAULT_API_URL) {
120
+ console.log('\n🔐 DayBy Authentication\n');
121
+ console.log(`Connecting to ${apiUrl}...`);
122
+ // 1. Request a device code
123
+ const res = await fetch(`${apiUrl}/api/v2/auth/device`, { method: 'POST' });
124
+ if (!res.ok) {
125
+ console.error(`\n❌ Could not reach DayBy (HTTP ${res.status}). Check your connection.\n`);
126
+ process.exit(1);
127
+ }
128
+ const { code, verification_uri, expires_in } = await res.json();
129
+ const expiresMinutes = Math.round(expires_in / 60);
130
+ // 2. Open browser
131
+ console.log('Opening DayBy in your browser...\n');
132
+ console.log(` URL: ${verification_uri}`);
133
+ console.log(` Code: ${code}`);
134
+ console.log(` Expires in ${expiresMinutes} minutes\n`);
135
+ openBrowser(verification_uri);
136
+ // 3. Poll until the user clicks Authorize
137
+ console.log('Waiting for you to authorize in the browser...');
138
+ const token = await pollForToken(apiUrl, code);
139
+ // 4. Save
140
+ saveCredentials({ token, api_url: apiUrl });
141
+ console.log('\n✅ Authenticated! You can now use DayBy MCP.\n');
142
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ // We test the exported functions directly
40
+ const auth_js_1 = require("./auth.js");
41
+ const CREDENTIALS_DIR = path.join(process.env.HOME || '~', '.dayby');
42
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
43
+ const BACKUP_FILE = CREDENTIALS_FILE + '.test-backup';
44
+ (0, vitest_1.describe)('Auth', () => {
45
+ let originalCredentials = null;
46
+ (0, vitest_1.beforeEach)(() => {
47
+ // Back up existing credentials
48
+ try {
49
+ originalCredentials = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
50
+ }
51
+ catch {
52
+ originalCredentials = null;
53
+ }
54
+ });
55
+ (0, vitest_1.afterEach)(() => {
56
+ // Restore original credentials
57
+ if (originalCredentials) {
58
+ fs.writeFileSync(CREDENTIALS_FILE, originalCredentials, { mode: 0o600 });
59
+ }
60
+ });
61
+ (0, vitest_1.describe)('loadCredentials', () => {
62
+ (0, vitest_1.it)('returns null when no credentials file exists', () => {
63
+ // Temporarily rename the file
64
+ const tempPath = CREDENTIALS_FILE + '.tmp';
65
+ try {
66
+ fs.renameSync(CREDENTIALS_FILE, tempPath);
67
+ }
68
+ catch { }
69
+ try {
70
+ const result = (0, auth_js_1.loadCredentials)();
71
+ (0, vitest_1.expect)(result).toBeNull();
72
+ }
73
+ finally {
74
+ try {
75
+ fs.renameSync(tempPath, CREDENTIALS_FILE);
76
+ }
77
+ catch { }
78
+ }
79
+ });
80
+ (0, vitest_1.it)('returns credentials when file exists', () => {
81
+ const creds = (0, auth_js_1.loadCredentials)();
82
+ // If credentials exist on this machine, they should have token and api_url
83
+ if (creds) {
84
+ (0, vitest_1.expect)(creds).toHaveProperty('token');
85
+ (0, vitest_1.expect)(creds).toHaveProperty('api_url');
86
+ (0, vitest_1.expect)(typeof creds.token).toBe('string');
87
+ (0, vitest_1.expect)(typeof creds.api_url).toBe('string');
88
+ }
89
+ });
90
+ });
91
+ (0, vitest_1.describe)('getStoredToken', () => {
92
+ (0, vitest_1.it)('returns null for a non-matching API URL', () => {
93
+ const token = (0, auth_js_1.getStoredToken)('https://not-the-right-server.com');
94
+ (0, vitest_1.expect)(token).toBeNull();
95
+ });
96
+ (0, vitest_1.it)('returns token for the correct API URL', () => {
97
+ const token = (0, auth_js_1.getStoredToken)('https://dayby.dev');
98
+ // Token exists on this dev machine
99
+ if (token) {
100
+ (0, vitest_1.expect)(typeof token).toBe('string');
101
+ (0, vitest_1.expect)(token.length).toBeGreaterThan(0);
102
+ }
103
+ });
104
+ });
105
+ });
@@ -28,7 +28,11 @@ export interface PostsListResponse {
28
28
  export declare class DayByClient {
29
29
  private apiUrl;
30
30
  private apiKey;
31
- constructor(config: DayByConfig);
31
+ private resolveKey?;
32
+ constructor(config: DayByConfig & {
33
+ resolveKey?: () => string;
34
+ });
35
+ private getApiKey;
32
36
  private request;
33
37
  listPosts(page?: number, perPage?: number): Promise<PostsListResponse>;
34
38
  getPost(slug: string): Promise<{
@@ -7,14 +7,22 @@ exports.DayByClient = void 0;
7
7
  class DayByClient {
8
8
  apiUrl;
9
9
  apiKey;
10
+ resolveKey;
10
11
  constructor(config) {
11
12
  this.apiUrl = config.apiUrl.replace(/\/$/, '');
12
13
  this.apiKey = config.apiKey;
14
+ this.resolveKey = config.resolveKey;
15
+ }
16
+ getApiKey() {
17
+ if (this.resolveKey)
18
+ return this.resolveKey();
19
+ return this.apiKey;
13
20
  }
14
21
  async request(method, path, body) {
15
22
  const url = `${this.apiUrl}${path}`;
23
+ const apiKey = this.getApiKey();
16
24
  const headers = {
17
- 'Authorization': `Bearer ${this.apiKey}`,
25
+ 'Authorization': `Bearer ${apiKey}`,
18
26
  'Content-Type': 'application/json',
19
27
  'Accept': 'application/json',
20
28
  };
package/dist/index.d.ts CHANGED
@@ -6,5 +6,9 @@
6
6
  * All content is sanitized locally before it ever touches the network.
7
7
  *
8
8
  * Flow: draft_post → review → publish_post (nothing leaves without approval)
9
+ *
10
+ * Auth:
11
+ * Run `dayby-mcp auth` once to authorize via OAuth (no API key needed).
12
+ * Or set DAYBY_API_KEY env var for the legacy API key flow.
9
13
  */
10
14
  export {};
package/dist/index.js CHANGED
@@ -7,6 +7,10 @@
7
7
  * All content is sanitized locally before it ever touches the network.
8
8
  *
9
9
  * Flow: draft_post → review → publish_post (nothing leaves without approval)
10
+ *
11
+ * Auth:
12
+ * Run `dayby-mcp auth` once to authorize via OAuth (no API key needed).
13
+ * Or set DAYBY_API_KEY env var for the legacy API key flow.
10
14
  */
11
15
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
16
  if (k2 === undefined) k2 = k;
@@ -47,13 +51,29 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
47
51
  const zod_1 = require("zod");
48
52
  const sanitizer_js_1 = require("./sanitizer.js");
49
53
  const dayby_client_js_1 = require("./dayby-client.js");
54
+ const auth_js_1 = require("./auth.js");
55
+ const visibility_js_1 = require("./visibility.js");
50
56
  const fs = __importStar(require("fs"));
51
57
  const path = __importStar(require("path"));
58
+ // --- Auth subcommand ---
59
+ async function handleAuthCommand() {
60
+ const args = process.argv.slice(3); // after "auth"
61
+ if (args.includes('--logout')) {
62
+ (0, auth_js_1.clearCredentials)();
63
+ return;
64
+ }
65
+ const apiUrl = process.env.DAYBY_API_URL || 'https://dayby.dev';
66
+ await (0, auth_js_1.runAuthFlow)(apiUrl);
67
+ }
52
68
  function loadConfig() {
53
- // 1. Check env vars
54
69
  const apiUrl = process.env.DAYBY_API_URL || 'https://dayby.dev';
55
- const apiKey = process.env.DAYBY_API_KEY || '';
56
- // 2. Load sanitizer config from file if it exists
70
+ // 1. Env var takes priority (legacy)
71
+ let apiKey = process.env.DAYBY_API_KEY || '';
72
+ // 2. Fall back to stored token from `dayby-mcp auth`
73
+ if (!apiKey) {
74
+ apiKey = (0, auth_js_1.getStoredToken)(apiUrl) || '';
75
+ }
76
+ // 3. Load sanitizer config from file if it exists
57
77
  let sanitizerConfig = {};
58
78
  const configPaths = [
59
79
  path.join(process.env.HOME || '~', '.dayby', 'sanitizer.json'),
@@ -69,7 +89,7 @@ function loadConfig() {
69
89
  // File doesn't exist, that's fine
70
90
  }
71
91
  }
72
- // 3. Also load from env (comma-separated)
92
+ // 4. Also load from env (comma-separated)
73
93
  if (process.env.DAYBY_BLOCKED_TERMS) {
74
94
  sanitizerConfig.blockedTerms = [
75
95
  ...(sanitizerConfig.blockedTerms || []),
@@ -90,9 +110,21 @@ function generateDraftId() {
90
110
  }
91
111
  // --- Main ---
92
112
  async function main() {
113
+ // Handle `dayby-mcp auth [--logout]` subcommand
114
+ if (process.argv[2] === 'auth') {
115
+ await handleAuthCommand();
116
+ process.exit(0);
117
+ }
93
118
  const config = loadConfig();
94
119
  const sanitizer = new sanitizer_js_1.Sanitizer(config.sanitizer);
95
- const client = new dayby_client_js_1.DayByClient({ apiUrl: config.apiUrl, apiKey: config.apiKey });
120
+ const client = new dayby_client_js_1.DayByClient({
121
+ apiUrl: config.apiUrl,
122
+ apiKey: config.apiKey,
123
+ resolveKey: () => {
124
+ // Re-check env and stored credentials on every call
125
+ return process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey;
126
+ },
127
+ });
96
128
  const server = new mcp_js_1.McpServer({
97
129
  name: 'dayby',
98
130
  version: '0.1.0',
@@ -193,11 +225,12 @@ async function main() {
193
225
  content: [{ type: 'text', text: `❌ Draft not found: ${draft_id}. Use draft_post to create a new one.` }],
194
226
  };
195
227
  }
196
- if (!config.apiKey) {
228
+ const currentKey = process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey;
229
+ if (!currentKey) {
197
230
  return {
198
231
  content: [{
199
232
  type: 'text',
200
- text: '❌ No API key configured. Set DAYBY_API_KEY environment variable or add it to your MCP config.',
233
+ text: '❌ Not authenticated. Run `dayby-mcp auth` in your terminal to connect your DayBy account.',
201
234
  }],
202
235
  };
203
236
  }
@@ -206,7 +239,7 @@ async function main() {
206
239
  const result = await client.createPost({
207
240
  title: draft.sanitizedTitle,
208
241
  content: draft.sanitizedContent,
209
- visibility: draft.visibility,
242
+ visibility: (0, visibility_js_1.toApiVisibility)(draft.visibility),
210
243
  });
211
244
  let response = `✅ **Published to DayBy!**\n\n`;
212
245
  response += `**Title:** ${result.post.title}\n`;
@@ -241,9 +274,9 @@ async function main() {
241
274
  page: zod_1.z.number().default(1).describe('Page number'),
242
275
  per_page: zod_1.z.number().default(10).describe('Posts per page'),
243
276
  }, async ({ page, per_page }) => {
244
- if (!config.apiKey) {
277
+ if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
245
278
  return {
246
- content: [{ type: 'text', text: '❌ No API key configured.' }],
279
+ content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }],
247
280
  };
248
281
  }
249
282
  try {
@@ -252,7 +285,7 @@ async function main() {
252
285
  for (const post of result.posts) {
253
286
  response += `• **${post.title}** — ${post.url}\n`;
254
287
  response += ` ${post.content.slice(0, 100)}${post.content.length > 100 ? '...' : ''}\n`;
255
- response += ` _${post.created_at.slice(0, 10)} · ${post.visibility}_\n\n`;
288
+ response += ` _${post.created_at.slice(0, 10)} · ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}_\n\n`;
256
289
  }
257
290
  return { content: [{ type: 'text', text: response }] };
258
291
  }
@@ -271,8 +304,8 @@ async function main() {
271
304
  server.tool('get_post', 'Get a single DayBy post by its slug.', {
272
305
  slug: zod_1.z.string().describe('The post slug'),
273
306
  }, async ({ slug }) => {
274
- if (!config.apiKey) {
275
- return { content: [{ type: 'text', text: '❌ No API key configured.' }] };
307
+ if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
308
+ return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
276
309
  }
277
310
  try {
278
311
  const result = await client.getPost(slug);
@@ -280,7 +313,7 @@ async function main() {
280
313
  let response = `📄 **${post.title}**\n\n`;
281
314
  response += `**Slug:** ${post.slug}\n`;
282
315
  response += `**URL:** ${post.url}\n`;
283
- response += `**Visibility:** ${post.visibility}\n`;
316
+ response += `**Visibility:** ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}\n`;
284
317
  response += `**Created:** ${post.created_at.slice(0, 10)}\n\n`;
285
318
  response += post.content;
286
319
  return { content: [{ type: 'text', text: response }] };
@@ -300,8 +333,8 @@ async function main() {
300
333
  content: zod_1.z.string().optional().describe('New content (will be sanitized)'),
301
334
  visibility: zod_1.z.enum(['published', 'draft']).optional().describe('New visibility'),
302
335
  }, async ({ slug, title, content, visibility }) => {
303
- if (!config.apiKey) {
304
- return { content: [{ type: 'text', text: '❌ No API key configured.' }] };
336
+ if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
337
+ return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
305
338
  }
306
339
  const params = {};
307
340
  if (title)
@@ -309,7 +342,7 @@ async function main() {
309
342
  if (content)
310
343
  params.content = sanitizer.sanitize(content).clean;
311
344
  if (visibility)
312
- params.visibility = visibility;
345
+ params.visibility = (0, visibility_js_1.toApiVisibility)(visibility);
313
346
  if (Object.keys(params).length === 0) {
314
347
  return { content: [{ type: 'text', text: '❌ Provide at least one field to update (title, content, or visibility).' }] };
315
348
  }
@@ -319,7 +352,7 @@ async function main() {
319
352
  let response = `✅ **Post Updated**\n\n`;
320
353
  response += `**Title:** ${post.title}\n`;
321
354
  response += `**URL:** ${post.url}\n`;
322
- response += `**Visibility:** ${post.visibility}\n`;
355
+ response += `**Visibility:** ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}\n`;
323
356
  return { content: [{ type: 'text', text: response }] };
324
357
  }
325
358
  catch (e) {
@@ -334,8 +367,8 @@ async function main() {
334
367
  server.tool('delete_post', 'Permanently delete a DayBy post by its slug.', {
335
368
  slug: zod_1.z.string().describe('The post slug to delete'),
336
369
  }, async ({ slug }) => {
337
- if (!config.apiKey) {
338
- return { content: [{ type: 'text', text: '❌ No API key configured.' }] };
370
+ if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
371
+ return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
339
372
  }
340
373
  try {
341
374
  const result = await client.deletePost(slug);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const sanitizer_js_1 = require("./sanitizer.js");
5
+ (0, vitest_1.describe)('Sanitizer', () => {
6
+ const sanitizer = new sanitizer_js_1.Sanitizer();
7
+ (0, vitest_1.describe)('default patterns', () => {
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');
11
+ (0, vitest_1.expect)(result.stripped.length).toBeGreaterThan(0);
12
+ });
13
+ (0, vitest_1.it)('strips AWS access keys', () => {
14
+ const result = sanitizer.sanitize('Key: AKIAIOSFODNN7EXAMPLE');
15
+ (0, vitest_1.expect)(result.clean).not.toContain('AKIAIOSFODNN7EXAMPLE');
16
+ });
17
+ (0, vitest_1.it)('strips private IPs', () => {
18
+ const result = sanitizer.sanitize('Server at 192.168.1.100');
19
+ (0, vitest_1.expect)(result.clean).not.toContain('192.168.1.100');
20
+ (0, vitest_1.expect)(result.clean).toContain('[REDACTED]');
21
+ });
22
+ (0, vitest_1.it)('strips email addresses', () => {
23
+ const result = sanitizer.sanitize('Contact me at user@company.com');
24
+ (0, vitest_1.expect)(result.clean).not.toContain('user@company.com');
25
+ });
26
+ (0, vitest_1.it)('strips database URLs', () => {
27
+ const result = sanitizer.sanitize('postgres://user:pass@localhost/db');
28
+ (0, vitest_1.expect)(result.clean).not.toContain('postgres://');
29
+ });
30
+ (0, vitest_1.it)('strips file paths with usernames', () => {
31
+ const result = sanitizer.sanitize('File at /home/javier/project');
32
+ (0, vitest_1.expect)(result.clean).not.toContain('/home/javier');
33
+ });
34
+ (0, vitest_1.it)('strips JWT tokens', () => {
35
+ const result = sanitizer.sanitize('Token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.abc123def456');
36
+ (0, vitest_1.expect)(result.clean).not.toContain('eyJhbGci');
37
+ });
38
+ (0, vitest_1.it)('strips GitHub tokens', () => {
39
+ const result = sanitizer.sanitize('ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl');
40
+ (0, vitest_1.expect)(result.clean).not.toContain('ghp_');
41
+ });
42
+ });
43
+ (0, vitest_1.describe)('safe content', () => {
44
+ (0, vitest_1.it)('leaves clean text untouched', () => {
45
+ const text = 'I built a feature using React and TypeScript today.';
46
+ const result = sanitizer.sanitize(text);
47
+ (0, vitest_1.expect)(result.clean).toBe(text);
48
+ (0, vitest_1.expect)(result.stripped).toHaveLength(0);
49
+ });
50
+ (0, vitest_1.it)('leaves public IPs alone', () => {
51
+ const result = sanitizer.sanitize('Server at 8.8.8.8');
52
+ (0, vitest_1.expect)(result.clean).toContain('8.8.8.8');
53
+ });
54
+ });
55
+ (0, vitest_1.describe)('blocked terms', () => {
56
+ (0, vitest_1.it)('strips configured blocked terms', () => {
57
+ const s = new sanitizer_js_1.Sanitizer({ blockedTerms: ['Acme Corp'] });
58
+ const result = s.sanitize('Working on the Acme Corp project');
59
+ (0, vitest_1.expect)(result.clean).not.toContain('Acme Corp');
60
+ });
61
+ (0, vitest_1.it)('strips blocked names', () => {
62
+ const s = new sanitizer_js_1.Sanitizer({ blockedNames: ['John Doe'] });
63
+ const result = s.sanitize('Paired with John Doe on the fix');
64
+ (0, vitest_1.expect)(result.clean).not.toContain('John Doe');
65
+ });
66
+ });
67
+ (0, vitest_1.describe)('blocked domains', () => {
68
+ (0, vitest_1.it)('strips blocked domain URLs', () => {
69
+ const s = new sanitizer_js_1.Sanitizer({ blockedDomains: ['internal.corp.com'] });
70
+ const result = s.sanitize('Check https://jira.internal.corp.com/browse/TICK-123');
71
+ (0, vitest_1.expect)(result.clean).not.toContain('internal.corp.com');
72
+ (0, vitest_1.expect)(result.clean).toContain('[REDACTED-URL]');
73
+ });
74
+ });
75
+ (0, vitest_1.describe)('check', () => {
76
+ (0, vitest_1.it)('returns safe for clean content', () => {
77
+ const result = sanitizer.check('Just a normal post about coding.');
78
+ (0, vitest_1.expect)(result.safe).toBe(true);
79
+ (0, vitest_1.expect)(result.issues).toHaveLength(0);
80
+ });
81
+ (0, vitest_1.it)('returns unsafe for sensitive content', () => {
82
+ const result = sanitizer.check('My api_key=super_secret_key_1234567890');
83
+ (0, vitest_1.expect)(result.safe).toBe(false);
84
+ (0, vitest_1.expect)(result.issues.length).toBeGreaterThan(0);
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Visibility mapping between MCP (user-facing) and DayBy API values.
3
+ *
4
+ * MCP uses "published"/"draft" (intuitive for users).
5
+ * DayBy API uses "publik"/"draft"/"hidden" (Rails enum).
6
+ */
7
+ export declare function toApiVisibility(v: string): string;
8
+ export declare function fromApiVisibility(v: string): string;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * Visibility mapping between MCP (user-facing) and DayBy API values.
4
+ *
5
+ * MCP uses "published"/"draft" (intuitive for users).
6
+ * DayBy API uses "publik"/"draft"/"hidden" (Rails enum).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.toApiVisibility = toApiVisibility;
10
+ exports.fromApiVisibility = fromApiVisibility;
11
+ function toApiVisibility(v) {
12
+ return v === 'published' ? 'publik' : v;
13
+ }
14
+ function fromApiVisibility(v) {
15
+ return v === 'publik' ? 'published' : v;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const visibility_js_1 = require("./visibility.js");
5
+ (0, vitest_1.describe)('Visibility mapping', () => {
6
+ (0, vitest_1.describe)('toApiVisibility', () => {
7
+ (0, vitest_1.it)('maps "published" to "publik"', () => {
8
+ (0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('published')).toBe('publik');
9
+ });
10
+ (0, vitest_1.it)('passes "draft" through unchanged', () => {
11
+ (0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('draft')).toBe('draft');
12
+ });
13
+ (0, vitest_1.it)('passes "hidden" through unchanged', () => {
14
+ (0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('hidden')).toBe('hidden');
15
+ });
16
+ });
17
+ (0, vitest_1.describe)('fromApiVisibility', () => {
18
+ (0, vitest_1.it)('maps "publik" to "published"', () => {
19
+ (0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('publik')).toBe('published');
20
+ });
21
+ (0, vitest_1.it)('passes "draft" through unchanged', () => {
22
+ (0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('draft')).toBe('draft');
23
+ });
24
+ (0, vitest_1.it)('passes "hidden" through unchanged', () => {
25
+ (0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('hidden')).toBe('hidden');
26
+ });
27
+ });
28
+ (0, vitest_1.describe)('round-trip', () => {
29
+ (0, vitest_1.it)('published -> publik -> published', () => {
30
+ (0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)((0, visibility_js_1.toApiVisibility)('published'))).toBe('published');
31
+ });
32
+ (0, vitest_1.it)('draft -> draft -> draft', () => {
33
+ (0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)((0, visibility_js_1.toApiVisibility)('draft'))).toBe('draft');
34
+ });
35
+ });
36
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayby/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
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": {
@@ -13,15 +13,25 @@
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "dev": "tsc --watch",
16
- "start": "node dist/index.js"
16
+ "start": "node dist/index.js",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest"
17
19
  },
18
- "keywords": ["mcp", "dayby", "developer", "journal", "claude"],
20
+ "keywords": [
21
+ "mcp",
22
+ "dayby",
23
+ "developer",
24
+ "journal",
25
+ "claude"
26
+ ],
19
27
  "license": "MIT",
20
28
  "dependencies": {
21
- "@modelcontextprotocol/sdk": "^1.0.0"
29
+ "@modelcontextprotocol/sdk": "^1.0.0",
30
+ "zod": "^3.22.0"
22
31
  },
23
32
  "devDependencies": {
33
+ "@types/node": "^20.0.0",
24
34
  "typescript": "^5.3.0",
25
- "@types/node": "^20.0.0"
35
+ "vitest": "^4.1.5"
26
36
  }
27
37
  }