codeblog-mcp 2.8.2 → 2.9.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.
@@ -42,7 +42,7 @@ function meIsInAgentList(meAgentId, agents) {
42
42
  return agents.some((agent) => agent.id === meAgentId);
43
43
  }
44
44
  function clearPollutedConfigAndBlock() {
45
- saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
45
+ saveConfig({ auth: { apiKey: undefined, userId: undefined, activeAgent: undefined } });
46
46
  return {
47
47
  content: [text(`Security alert: Your CodeBlog API key appears polluted and is resolving to an agent ` +
48
48
  `that does not belong to your account. We cleared local config to protect your identity.\n\n` +
@@ -60,7 +60,7 @@ async function verifyIdentity(apiKey, serverUrl) {
60
60
  return null;
61
61
  identityVerified = true;
62
62
  const config = loadConfig();
63
- if (!config.userId) {
63
+ if (!config.auth?.userId) {
64
64
  // Legacy config without userId — backfill it on first run
65
65
  try {
66
66
  const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
@@ -77,7 +77,7 @@ async function verifyIdentity(apiKey, serverUrl) {
77
77
  return clearPollutedConfigAndBlock();
78
78
  }
79
79
  if (remoteUserId) {
80
- saveConfig({ userId: remoteUserId });
80
+ saveConfig({ auth: { userId: remoteUserId } });
81
81
  }
82
82
  }
83
83
  }
@@ -94,7 +94,7 @@ async function verifyIdentity(apiKey, serverUrl) {
94
94
  });
95
95
  if (!res.ok) {
96
96
  // API key invalid — clear config
97
- saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
97
+ saveConfig({ auth: { apiKey: undefined, userId: undefined, activeAgent: undefined } });
98
98
  return {
99
99
  content: [text(`Your API key is invalid or expired. Please run codeblog_setup again.\n\n` +
100
100
  SETUP_GUIDE)],
@@ -109,9 +109,9 @@ async function verifyIdentity(apiKey, serverUrl) {
109
109
  if (meInList === false) {
110
110
  return clearPollutedConfigAndBlock();
111
111
  }
112
- if (remoteUserId && remoteUserId !== config.userId) {
112
+ if (remoteUserId && remoteUserId !== config.auth?.userId) {
113
113
  // IDENTITY MISMATCH — config was polluted by another user's API key
114
- saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
114
+ saveConfig({ auth: { apiKey: undefined, userId: undefined, activeAgent: undefined } });
115
115
  return {
116
116
  content: [text(`Security alert: Your CodeBlog config was using a different user's API key. ` +
117
117
  `The config has been cleared for your protection.\n\n` +
@@ -1,17 +1,20 @@
1
1
  export declare const CONFIG_DIR: string;
2
2
  export declare const CONFIG_FILE: string;
3
- export interface CodeblogConfig {
3
+ export interface AuthConfig {
4
4
  apiKey?: string;
5
- userId?: string;
6
- url?: string;
7
- defaultLanguage?: string;
8
5
  activeAgent?: string;
6
+ userId?: string;
7
+ }
8
+ export interface CodeblogConfig {
9
+ serverUrl?: string;
10
+ dailyReportHour?: number;
11
+ auth?: AuthConfig;
12
+ cli?: Record<string, unknown>;
9
13
  }
10
14
  export declare function loadConfig(): CodeblogConfig;
11
- export declare function saveConfig(config: CodeblogConfig): void;
15
+ export declare function saveConfig(config: Partial<CodeblogConfig>): void;
12
16
  export declare function getApiKey(): string;
13
17
  export declare function getUrl(): string;
14
- export declare function getLanguage(): string | undefined;
15
18
  export declare const SETUP_GUIDE: string;
16
19
  export declare const text: (t: string) => {
17
20
  type: "text";
@@ -13,22 +13,35 @@ export function loadConfig() {
13
13
  catch { }
14
14
  return {};
15
15
  }
16
+ function deepMerge(target, source) {
17
+ const result = { ...target };
18
+ for (const key of Object.keys(source)) {
19
+ const val = source[key];
20
+ if (val === undefined) {
21
+ delete result[key];
22
+ }
23
+ else if (typeof val === "object" && !Array.isArray(val) && val !== null) {
24
+ result[key] = deepMerge(result[key] || {}, val);
25
+ }
26
+ else {
27
+ result[key] = val;
28
+ }
29
+ }
30
+ return result;
31
+ }
16
32
  export function saveConfig(config) {
17
33
  if (!fs.existsSync(CONFIG_DIR)) {
18
34
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
19
35
  }
20
36
  const existing = loadConfig();
21
- const merged = { ...existing, ...config };
37
+ const merged = deepMerge(existing, config);
22
38
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
23
39
  }
24
40
  export function getApiKey() {
25
- return process.env.CODEBLOG_API_KEY || loadConfig().apiKey || "";
41
+ return process.env.CODEBLOG_API_KEY || loadConfig().auth?.apiKey || "";
26
42
  }
27
43
  export function getUrl() {
28
- return process.env.CODEBLOG_URL || loadConfig().url || "https://codeblog.ai";
29
- }
30
- export function getLanguage() {
31
- return process.env.CODEBLOG_LANGUAGE || loadConfig().defaultLanguage;
44
+ return process.env.CODEBLOG_URL || loadConfig().serverUrl || "https://codeblog.ai";
32
45
  }
33
46
  export const SETUP_GUIDE = `CodeBlog is not set up yet. To get started, run the codeblog_setup tool.\n\n` +
34
47
  `• New user: provide email + username + password → codeblog_setup(email, username, password)\n` +
@@ -55,7 +55,7 @@ export function registerAgentTools(server) {
55
55
  let output = `## Your Agents (${agents.length})\n\n`;
56
56
  const config = loadConfig();
57
57
  for (const a of agents) {
58
- const isCurrent = a.is_current || a.name === config.activeAgent;
58
+ const isCurrent = a.is_current || a.name === config.auth?.activeAgent;
59
59
  const marker = isCurrent ? " ← current" : "";
60
60
  output += `- **${a.name}** (${a.source_type})${marker}\n`;
61
61
  output += ` ID: \`${a.id}\` | Posts: ${a.posts_count} | Created: ${a.created_at}\n`;
@@ -174,7 +174,7 @@ export function registerAgentTools(server) {
174
174
  const data = await res.json();
175
175
  const target = data.agent;
176
176
  // Save the target agent's API key and name to config
177
- saveConfig({ apiKey: target.api_key, activeAgent: target.name });
177
+ saveConfig({ auth: { apiKey: target.api_key, activeAgent: target.name } });
178
178
  return {
179
179
  content: [
180
180
  text(`✅ Switched to agent **${target.name}** (${target.source_type})!\n\n` +
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { text } from "../lib/config.js";
3
- import { loadClientConfig, saveClientConfig } from "../lib/client-config.js";
2
+ import { text, loadConfig, saveConfig } from "../lib/config.js";
4
3
  import { withAuth } from "../lib/auth-guard.js";
5
4
  import { collectDailyUsage, formatTokens, formatCost, } from "../lib/usage-collector.js";
6
5
  // ─── Tool registration ───────────────────────────────────────────────
@@ -271,7 +270,7 @@ export function registerDailyReportTools(server) {
271
270
  },
272
271
  }, async ({ auto_hour, get }) => {
273
272
  if (get) {
274
- const cfg = loadClientConfig();
273
+ const cfg = loadConfig();
275
274
  const hour = normalizeDailyReportHour(cfg.dailyReportHour);
276
275
  const enabled = hour >= 0;
277
276
  return {
@@ -293,7 +292,7 @@ export function registerDailyReportTools(server) {
293
292
  ],
294
293
  };
295
294
  }
296
- saveClientConfig({ dailyReportHour: auto_hour });
295
+ saveConfig({ dailyReportHour: auto_hour });
297
296
  const enabled = auto_hour >= 0;
298
297
  return {
299
298
  content: [
@@ -35,9 +35,11 @@ export function registerSetupTools(server, PKG_VERSION) {
35
35
  }
36
36
  const data = await res.json();
37
37
  const resolvedUserId = data.agent?.userId || data.userId;
38
- const config = { apiKey: result.api_key, activeAgent: data.agent.name, userId: resolvedUserId };
38
+ const config = {
39
+ auth: { apiKey: result.api_key, activeAgent: data.agent.name, userId: resolvedUserId },
40
+ };
39
41
  if (url)
40
- config.url = url;
42
+ config.serverUrl = url;
41
43
  saveConfig(config);
42
44
  // Check if user has multiple agents
43
45
  let multiAgentNote = "";
@@ -105,12 +107,10 @@ export function registerSetupTools(server, PKG_VERSION) {
105
107
  // Use the first activated agent
106
108
  const agent = data.agents[0];
107
109
  const config = {
108
- apiKey: agent.api_key,
109
- activeAgent: agent.name,
110
- userId: data.user.id,
110
+ auth: { apiKey: agent.api_key, activeAgent: agent.name, userId: data.user.id },
111
111
  };
112
112
  if (url)
113
- config.url = url;
113
+ config.serverUrl = url;
114
114
  saveConfig(config);
115
115
  const agentList = data.agents.map((a) => ` • ${a.name} (${a.posts_count} posts)`).join("\n");
116
116
  const multiAgentPrompt = data.agents.length > 1
@@ -154,9 +154,11 @@ export function registerSetupTools(server, PKG_VERSION) {
154
154
  if (!res.ok) {
155
155
  return { content: [text(`Setup failed: ${data.error || "Unknown error"}`)], isError: true };
156
156
  }
157
- const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name, userId: data.user.id };
157
+ const config = {
158
+ auth: { apiKey: data.agent.api_key, activeAgent: data.agent.name, userId: data.user.id },
159
+ };
158
160
  if (url)
159
- config.url = url;
161
+ config.serverUrl = url;
160
162
  saveConfig(config);
161
163
  return {
162
164
  content: [text(`✅ CodeBlog setup complete!\n\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeblog-mcp",
3
- "version": "2.8.2",
3
+ "version": "2.9.0",
4
4
  "description": "CodeBlog MCP server — 29 tools for AI agents to fully participate in a coding forum. Scan 9 IDEs, auto-post insights, generate daily reports, manage agents, edit/delete posts, bookmark, notifications, follow users, weekly digest, trending topics, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,3 +0,0 @@
1
- export declare function getClientConfigPath(): string;
2
- export declare function loadClientConfig(): Record<string, unknown>;
3
- export declare function saveClientConfig(partial: Record<string, unknown>): void;
@@ -1,44 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
- // The CLI/TUI client uses XDG-compliant paths (~/.config/codeblog/config.json)
5
- // while the MCP server uses ~/.codeblog/config.json.
6
- // This module reads/writes the CLIENT config so MCP tools can configure
7
- // client-side behavior (e.g. daily report auto-trigger hour).
8
- function getClientConfigDir() {
9
- const home = os.homedir();
10
- const xdgConfig = process.env.XDG_CONFIG_HOME;
11
- if (process.platform === "win32") {
12
- return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "codeblog");
13
- }
14
- return path.join(xdgConfig || path.join(home, ".config"), "codeblog");
15
- }
16
- export function getClientConfigPath() {
17
- return path.join(getClientConfigDir(), "config.json");
18
- }
19
- export function loadClientConfig() {
20
- try {
21
- const filePath = getClientConfigPath();
22
- if (fs.existsSync(filePath)) {
23
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
24
- }
25
- }
26
- catch { }
27
- return {};
28
- }
29
- export function saveClientConfig(partial) {
30
- const dir = getClientConfigDir();
31
- if (!fs.existsSync(dir)) {
32
- fs.mkdirSync(dir, { recursive: true });
33
- }
34
- const existing = loadClientConfig();
35
- const merged = { ...existing, ...partial };
36
- const filePath = getClientConfigPath();
37
- fs.writeFileSync(filePath, JSON.stringify(merged, null, 2));
38
- try {
39
- fs.chmodSync(filePath, 0o600);
40
- }
41
- catch {
42
- // Best-effort on non-POSIX platforms.
43
- }
44
- }