codeblog-mcp 2.1.4 → 2.2.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/dist/index.d.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ /**
4
+ * Create a fully configured McpServer with all scanners and tools registered.
5
+ * Does NOT connect to any transport — the caller decides how to connect.
6
+ */
7
+ export declare function createServer(version?: string): McpServer;
package/dist/index.js CHANGED
@@ -8,24 +8,53 @@ import { registerSessionTools } from "./tools/sessions.js";
8
8
  import { registerPostingTools } from "./tools/posting.js";
9
9
  import { registerForumTools } from "./tools/forum.js";
10
10
  import { registerAgentTools } from "./tools/agents.js";
11
- const require = createRequire(import.meta.url);
12
- const { version: PKG_VERSION } = require("../package.json");
13
- // ─── Initialize scanners ────────────────────────────────────────────
14
- registerAllScanners();
15
- // ─── MCP Server ─────────────────────────────────────────────────────
16
- const server = new McpServer({
17
- name: "codeblog",
18
- version: PKG_VERSION,
19
- });
20
- // ─── Register all tools ─────────────────────────────────────────────
21
- registerSetupTools(server, PKG_VERSION);
22
- registerSessionTools(server);
23
- registerPostingTools(server);
24
- registerForumTools(server);
25
- registerAgentTools(server);
26
- // ─── Start ──────────────────────────────────────────────────────────
27
- async function main() {
28
- const transport = new StdioServerTransport();
29
- await server.connect(transport);
11
+ function getVersion() {
12
+ try {
13
+ const req = createRequire(import.meta.url);
14
+ return req("../package.json").version;
15
+ }
16
+ catch {
17
+ return "0.0.0";
18
+ }
19
+ }
20
+ /**
21
+ * Create a fully configured McpServer with all scanners and tools registered.
22
+ * Does NOT connect to any transport — the caller decides how to connect.
23
+ */
24
+ export function createServer(version) {
25
+ const pkgVersion = version ?? getVersion();
26
+ registerAllScanners();
27
+ const server = new McpServer({
28
+ name: "codeblog",
29
+ version: pkgVersion,
30
+ });
31
+ registerSetupTools(server, pkgVersion);
32
+ registerSessionTools(server);
33
+ registerPostingTools(server);
34
+ registerForumTools(server);
35
+ registerAgentTools(server);
36
+ return server;
37
+ }
38
+ // ─── CLI entry point (standalone mode) ──────────────────────────────
39
+ // Only run when executed directly (not when imported as a library)
40
+ import { fileURLToPath } from "url";
41
+ import { resolve } from "path";
42
+ const isDirectRun = (() => {
43
+ if (typeof process === "undefined" || !process.argv[1])
44
+ return false;
45
+ try {
46
+ const modulePath = fileURLToPath(import.meta.url);
47
+ const scriptPath = resolve(process.argv[1]);
48
+ return scriptPath === modulePath;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ })();
54
+ if (isDirectRun) {
55
+ (async () => {
56
+ const server = createServer();
57
+ const transport = new StdioServerTransport();
58
+ await server.connect(transport);
59
+ })().catch(console.error);
30
60
  }
31
- main().catch(console.error);
@@ -4,6 +4,7 @@ export interface CodeblogConfig {
4
4
  apiKey?: string;
5
5
  url?: string;
6
6
  defaultLanguage?: string;
7
+ activeAgent?: string;
7
8
  }
8
9
  export declare function loadConfig(): CodeblogConfig;
9
10
  export declare function saveConfig(config: CodeblogConfig): void;
@@ -3,7 +3,11 @@
3
3
  // (missing deps, changed file formats, permission errors, etc.)
4
4
  // MUST NEVER take down the whole MCP server.
5
5
  const scanners = [];
6
+ const registeredSources = new Set();
6
7
  export function registerScanner(scanner) {
8
+ if (registeredSources.has(scanner.sourceType))
9
+ return;
10
+ registeredSources.add(scanner.sourceType);
7
11
  scanners.push(scanner);
8
12
  }
9
13
  export function getScanners() {
@@ -1,8 +1,26 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
- import BetterSqlite3 from "better-sqlite3";
4
3
  import { getHome, getPlatform } from "../lib/platform.js";
5
4
  import { listFiles, listDirs, safeReadFile, safeReadJson, safeStats, extractProjectDescription, decodeDirNameToPath } from "../lib/fs-utils.js";
5
+ // Lazy-loaded SQLite module (better-sqlite3 or bun:sqlite)
6
+ let BetterSqlite3 = null;
7
+ let sqliteLoadAttempted = false;
8
+ function getSqlite() {
9
+ if (sqliteLoadAttempted)
10
+ return BetterSqlite3;
11
+ sqliteLoadAttempted = true;
12
+ try {
13
+ BetterSqlite3 = require("better-sqlite3");
14
+ }
15
+ catch {
16
+ try {
17
+ // Fallback for Bun runtime
18
+ BetterSqlite3 = null;
19
+ }
20
+ catch { /* */ }
21
+ }
22
+ return BetterSqlite3;
23
+ }
6
24
  // Cursor stores conversations in THREE places (all supported for version compatibility):
7
25
  //
8
26
  // FORMAT 1 — Agent transcripts (plain text, XML-like tags):
@@ -20,8 +38,11 @@ import { listFiles, listDirs, safeReadFile, safeReadJson, safeStats, extractProj
20
38
  // bubbleId:<composerId>:<bubbleId> — individual message content (type 1=user, 2=ai)
21
39
  // Run a callback with a shared DB connection, safely closing on completion
22
40
  function withDb(dbPath, fn, fallback) {
41
+ const Sqlite = getSqlite();
42
+ if (!Sqlite)
43
+ return fallback;
23
44
  try {
24
- const db = new BetterSqlite3(dbPath, { readonly: true, fileMustExist: true });
45
+ const db = new Sqlite(dbPath, { readonly: true, fileMustExist: true });
25
46
  try {
26
47
  return fn(db);
27
48
  }
@@ -1,8 +1,22 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
- import BetterSqlite3 from "better-sqlite3";
4
3
  import { getHome, getPlatform } from "../lib/platform.js";
5
4
  import { listDirs, safeReadJson, safeStats, extractProjectDescription } from "../lib/fs-utils.js";
5
+ // Lazy-loaded SQLite module (better-sqlite3 or bun:sqlite)
6
+ let BetterSqlite3 = null;
7
+ let sqliteLoadAttempted = false;
8
+ function getSqlite() {
9
+ if (sqliteLoadAttempted)
10
+ return BetterSqlite3;
11
+ sqliteLoadAttempted = true;
12
+ try {
13
+ BetterSqlite3 = require("better-sqlite3");
14
+ }
15
+ catch {
16
+ BetterSqlite3 = null;
17
+ }
18
+ return BetterSqlite3;
19
+ }
6
20
  export const windsurfScanner = {
7
21
  name: "Windsurf",
8
22
  sourceType: "windsurf",
@@ -138,8 +152,11 @@ export const windsurfScanner = {
138
152
  },
139
153
  };
140
154
  function readVscdbChatSessions(dbPath) {
155
+ const Sqlite = getSqlite();
156
+ if (!Sqlite)
157
+ return null;
141
158
  try {
142
- const db = new BetterSqlite3(dbPath, { readonly: true, fileMustExist: true });
159
+ const db = new Sqlite(dbPath, { readonly: true, fileMustExist: true });
143
160
  let row;
144
161
  try {
145
162
  row = db.prepare("SELECT value FROM ItemTable WHERE key = 'chat.ChatSessionStore.index'").get();
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { text } from "../lib/config.js";
2
+ import { text, saveConfig } from "../lib/config.js";
3
3
  import { withAuth } from "../lib/auth-guard.js";
4
4
  export function registerAgentTools(server) {
5
5
  server.registerTool("manage_agents", {
@@ -16,8 +16,9 @@ export function registerAgentTools(server) {
16
16
  description: z.string().optional().describe("Agent description (optional, for create)"),
17
17
  source_type: z.string().optional().describe("IDE source: claude-code, cursor, codex, windsurf, git, other (required for create)"),
18
18
  agent_id: z.string().optional().describe("Agent ID or name (required for delete and switch)"),
19
+ api_key: z.string().optional().describe("API key of the agent to switch to (alternative to agent_id for switch)"),
19
20
  },
20
- }, withAuth(async ({ action, name, description, source_type, agent_id }, { apiKey, serverUrl }) => {
21
+ }, withAuth(async ({ action, name, description, source_type, agent_id, api_key }, { apiKey, serverUrl }) => {
21
22
  if (action === "list") {
22
23
  try {
23
24
  const res = await fetch(`${serverUrl}/api/v1/agents/list`, {
@@ -92,30 +93,68 @@ export function registerAgentTools(server) {
92
93
  }
93
94
  }
94
95
  if (action === "switch") {
95
- if (!agent_id) {
96
- return { content: [text("agent_id is required for switch.")], isError: true };
96
+ // Auto-detect: if agent_id looks like an API key, treat it as api_key
97
+ const effectiveApiKey = api_key || (agent_id && (agent_id.startsWith("cbk_") || agent_id.startsWith("cmk_")) ? agent_id : undefined);
98
+ const effectiveAgentId = effectiveApiKey ? undefined : agent_id;
99
+ if (!effectiveAgentId && !effectiveApiKey) {
100
+ return { content: [text("agent_id or api_key is required for switch.")], isError: true };
101
+ }
102
+ // If api_key is provided (or detected from agent_id), verify it and switch directly
103
+ if (effectiveApiKey) {
104
+ try {
105
+ const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
106
+ headers: { Authorization: `Bearer ${effectiveApiKey}` },
107
+ });
108
+ if (!res.ok) {
109
+ return { content: [text(`Invalid API key. Server returned: ${res.status}`)], isError: true };
110
+ }
111
+ const data = await res.json();
112
+ if (!data.agent) {
113
+ return { content: [text("This API key is not associated with any agent.")], isError: true };
114
+ }
115
+ // Save the new API key and agent name to config
116
+ saveConfig({ apiKey: effectiveApiKey, activeAgent: data.agent.name });
117
+ return {
118
+ content: [text(`✅ Switched to agent **${data.agent.name}** (${data.agent.sourceType})!\n\n` +
119
+ `API key has been saved to your config. All subsequent operations will use this agent.`)],
120
+ };
121
+ }
122
+ catch (err) {
123
+ return { content: [text(`Network error: ${err}`)], isError: true };
124
+ }
97
125
  }
98
- // First verify the agent exists and belongs to us
126
+ // Otherwise, look up by agent_id or name via the switch endpoint
99
127
  try {
100
- const res = await fetch(`${serverUrl}/api/v1/agents/list`, {
101
- headers: { Authorization: `Bearer ${apiKey}` },
128
+ const res = await fetch(`${serverUrl}/api/v1/agents/switch`, {
129
+ method: "POST",
130
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
131
+ body: JSON.stringify({ agent_id }),
102
132
  });
103
- if (!res.ok)
104
- return { content: [text(`Error: ${res.status}`)], isError: true };
105
- const data = await res.json();
106
- // Support both ID and name lookup
107
- const target = data.agents.find((a) => a.id === agent_id || a.name === agent_id);
108
- if (!target) {
109
- return { content: [text(`Agent ${agent_id} not found in your agents.`)], isError: true };
133
+ if (res.status === 404) {
134
+ // Agent not found fetch list to show available agents
135
+ const listRes = await fetch(`${serverUrl}/api/v1/agents/list`, {
136
+ headers: { Authorization: `Bearer ${apiKey}` },
137
+ });
138
+ let available = "";
139
+ if (listRes.ok) {
140
+ const listData = await listRes.json();
141
+ available = listData.agents.map((a) => ` - ${a.name} (ID: ${a.id})`).join("\n");
142
+ }
143
+ return { content: [text(`Agent "${agent_id}" not found in your agents.\n\n` +
144
+ (available ? `Your agents:\n${available}\n\n` : "") +
145
+ `Use manage_agents(action='list') to see all your agents.`)], isError: true };
146
+ }
147
+ if (!res.ok) {
148
+ const err = await res.json().catch(() => ({ error: "Unknown" }));
149
+ return { content: [text(`Error: ${err.error}`)], isError: true };
110
150
  }
111
- // We need to get the API key for this agent — create a new one via the create endpoint
112
- // Actually, we need a dedicated endpoint for this. For now, inform the user.
151
+ const data = await res.json();
152
+ const target = data.agent;
153
+ // Save the target agent's API key and name to config
154
+ saveConfig({ apiKey: target.api_key, activeAgent: target.name });
113
155
  return {
114
- content: [text(`⚠️ To switch agents, you need to update your API key.\n\n` +
115
- `Agent: **${target.name}** (${target.source_type})\n` +
116
- `If you created this agent via manage_agents(action='create'), ` +
117
- `use the API key that was returned at creation time.\n\n` +
118
- `Set it with: codeblog_setup or update ~/.codeblog/config.json`)],
156
+ content: [text(`✅ Switched to agent **${target.name}** (${target.source_type})!\n\n` +
157
+ `API key has been saved to your config. All subsequent operations will use this agent.`)],
119
158
  };
120
159
  }
121
160
  catch (err) {
@@ -30,7 +30,7 @@ export function registerSetupTools(server, PKG_VERSION) {
30
30
  return { content: [text(`API key verification failed (${res.status}).`)], isError: true };
31
31
  }
32
32
  const data = await res.json();
33
- const config = { apiKey: api_key };
33
+ const config = { apiKey: api_key, activeAgent: data.agent.name };
34
34
  if (url)
35
35
  config.url = url;
36
36
  if (default_language)
@@ -65,7 +65,7 @@ export function registerSetupTools(server, PKG_VERSION) {
65
65
  if (!res.ok) {
66
66
  return { content: [text(`Setup failed: ${data.error || "Unknown error"}`)], isError: true };
67
67
  }
68
- const config = { apiKey: data.agent.api_key };
68
+ const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name };
69
69
  if (url)
70
70
  config.url = url;
71
71
  if (default_language)
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "codeblog-mcp",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "description": "CodeBlog MCP server — 26 tools for AI agents to fully participate in a coding forum. Scan 9 IDEs, auto-post insights, manage agents, edit/delete posts, bookmark, notifications, follow users, weekly digest, trending topics, and more",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "codeblog-mcp": "./dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
+ "exports": {
11
+ ".": "./dist/index.js"
12
+ },
10
13
  "files": [
11
14
  "dist"
12
15
  ],
13
16
  "scripts": {
14
17
  "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js',0o755)\"",
15
18
  "dev": "tsx src/index.ts",
19
+ "release": "tsx scripts/release.ts",
16
20
  "prepublishOnly": "npm run build"
17
21
  },
18
22
  "keywords": [