codeblog-mcp 2.2.1 → 2.3.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.
@@ -19,7 +19,8 @@ export declare function requireAuth(): {
19
19
  export declare function isAuthError(result: ReturnType<typeof requireAuth>): result is ToolResult;
20
20
  /**
21
21
  * Wrap a tool handler that requires authentication.
22
- * Automatically checks API key and injects { apiKey, serverUrl } into the handler.
22
+ * Automatically checks API key, verifies identity on first call,
23
+ * and injects { apiKey, serverUrl } into the handler.
23
24
  */
24
25
  export declare function withAuth<TArgs, TResult>(handler: (args: TArgs, ctx: {
25
26
  apiKey: string;
@@ -1,4 +1,4 @@
1
- import { getApiKey, getUrl, text, SETUP_GUIDE } from "./config.js";
1
+ import { getApiKey, getUrl, loadConfig, saveConfig, text, SETUP_GUIDE } from "./config.js";
2
2
  /**
3
3
  * Pre-check: ensure API key is configured.
4
4
  * Returns { apiKey, serverUrl } on success, or a ToolResult error to return early.
@@ -16,15 +16,84 @@ export function requireAuth() {
16
16
  export function isAuthError(result) {
17
17
  return "content" in result && "isError" in result;
18
18
  }
19
+ // ─── Identity verification (runs once per session) ──────────────────
20
+ let identityVerified = false;
21
+ /**
22
+ * Verify that the stored API key matches the stored userId.
23
+ * If mismatch is detected (config was polluted by another user's key),
24
+ * clear the config and force re-setup.
25
+ */
26
+ async function verifyIdentity(apiKey, serverUrl) {
27
+ if (identityVerified)
28
+ return null;
29
+ identityVerified = true;
30
+ const config = loadConfig();
31
+ if (!config.userId) {
32
+ // Legacy config without userId — backfill it on first run
33
+ try {
34
+ const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
35
+ headers: { Authorization: `Bearer ${apiKey}` },
36
+ });
37
+ if (res.ok) {
38
+ const data = await res.json();
39
+ const remoteUserId = data.agent?.userId || data.userId;
40
+ if (remoteUserId) {
41
+ saveConfig({ userId: remoteUserId });
42
+ }
43
+ }
44
+ }
45
+ catch {
46
+ // Network error on first run — skip verification, will retry next time
47
+ identityVerified = false;
48
+ }
49
+ return null;
50
+ }
51
+ // Config has a userId — verify it matches the API key
52
+ try {
53
+ const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
54
+ headers: { Authorization: `Bearer ${apiKey}` },
55
+ });
56
+ if (!res.ok) {
57
+ // API key invalid — clear config
58
+ saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
59
+ return {
60
+ content: [text(`Your API key is invalid or expired. Please run codeblog_setup again.\n\n` +
61
+ SETUP_GUIDE)],
62
+ isError: true,
63
+ };
64
+ }
65
+ const data = await res.json();
66
+ const remoteUserId = data.agent?.userId || data.userId;
67
+ if (remoteUserId && remoteUserId !== config.userId) {
68
+ // IDENTITY MISMATCH — config was polluted by another user's API key
69
+ saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
70
+ return {
71
+ content: [text(`Security alert: Your CodeBlog config was using a different user's API key. ` +
72
+ `The config has been cleared for your protection.\n\n` +
73
+ `Please run codeblog_setup with your own API key to reconfigure.`)],
74
+ isError: true,
75
+ };
76
+ }
77
+ }
78
+ catch {
79
+ // Network error — skip verification, will retry next time
80
+ identityVerified = false;
81
+ }
82
+ return null;
83
+ }
19
84
  /**
20
85
  * Wrap a tool handler that requires authentication.
21
- * Automatically checks API key and injects { apiKey, serverUrl } into the handler.
86
+ * Automatically checks API key, verifies identity on first call,
87
+ * and injects { apiKey, serverUrl } into the handler.
22
88
  */
23
89
  export function withAuth(handler) {
24
90
  return async (args) => {
25
91
  const auth = requireAuth();
26
92
  if (isAuthError(auth))
27
93
  return auth;
94
+ const identityError = await verifyIdentity(auth.apiKey, auth.serverUrl);
95
+ if (identityError)
96
+ return identityError;
28
97
  return handler(args, auth);
29
98
  };
30
99
  }
@@ -2,6 +2,7 @@ export declare const CONFIG_DIR: string;
2
2
  export declare const CONFIG_FILE: string;
3
3
  export interface CodeblogConfig {
4
4
  apiKey?: string;
5
+ userId?: string;
5
6
  url?: string;
6
7
  defaultLanguage?: string;
7
8
  activeAgent?: string;
@@ -14,11 +14,11 @@ export function registerAgentTools(server) {
14
14
  "'switch' = switch to a different agent"),
15
15
  name: z.string().optional().describe("Agent name (required for create)"),
16
16
  description: z.string().optional().describe("Agent description (optional, for create)"),
17
+ avatar: z.string().optional().describe("Agent avatar — emoji string, image URL, or base64 data URL (optional, for create)"),
17
18
  source_type: z.string().optional().describe("IDE source: claude-code, cursor, codex, windsurf, git, other (required for create)"),
18
19
  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)"),
20
20
  },
21
- }, withAuth(async ({ action, name, description, source_type, agent_id, api_key }, { apiKey, serverUrl }) => {
21
+ }, withAuth(async ({ action, name, description, avatar, source_type, agent_id }, { apiKey, serverUrl }) => {
22
22
  if (action === "list") {
23
23
  try {
24
24
  const res = await fetch(`${serverUrl}/api/v1/agents/list`, {
@@ -53,7 +53,7 @@ export function registerAgentTools(server) {
53
53
  const res = await fetch(`${serverUrl}/api/v1/agents/create`, {
54
54
  method: "POST",
55
55
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
56
- body: JSON.stringify({ name, description, source_type }),
56
+ body: JSON.stringify({ name, description, avatar, source_type }),
57
57
  });
58
58
  if (!res.ok) {
59
59
  const err = await res.json().catch(() => ({ error: "Unknown" }));
@@ -93,37 +93,10 @@ export function registerAgentTools(server) {
93
93
  }
94
94
  }
95
95
  if (action === "switch") {
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
- }
96
+ if (!agent_id) {
97
+ return { content: [text("agent_id is required for switch. Use manage_agents(action='list') to see your agents.")], isError: true };
125
98
  }
126
- // Otherwise, look up by agent_id or name via the switch endpoint
99
+ // Switch via the server endpoint which validates ownership (only allows switching to your own agents)
127
100
  try {
128
101
  const res = await fetch(`${serverUrl}/api/v1/agents/switch`, {
129
102
  method: "POST",
@@ -30,7 +30,8 @@ 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, activeAgent: data.agent.name };
33
+ const resolvedUserId = data.agent?.userId || data.userId;
34
+ const config = { apiKey: api_key, activeAgent: data.agent.name, userId: resolvedUserId };
34
35
  if (url)
35
36
  config.url = url;
36
37
  if (default_language)
@@ -65,7 +66,7 @@ export function registerSetupTools(server, PKG_VERSION) {
65
66
  if (!res.ok) {
66
67
  return { content: [text(`Setup failed: ${data.error || "Unknown error"}`)], isError: true };
67
68
  }
68
- const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name };
69
+ const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name, userId: data.user.id };
69
70
  if (url)
70
71
  config.url = url;
71
72
  if (default_language)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeblog-mcp",
3
- "version": "2.2.1",
3
+ "version": "2.3.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": {