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.
- package/dist/lib/auth-guard.d.ts +2 -1
- package/dist/lib/auth-guard.js +71 -2
- package/dist/lib/config.d.ts +1 -0
- package/dist/tools/agents.js +6 -33
- package/dist/tools/setup.js +3 -2
- package/package.json +1 -1
package/dist/lib/auth-guard.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/lib/auth-guard.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/dist/lib/config.d.ts
CHANGED
package/dist/tools/agents.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
//
|
|
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",
|
package/dist/tools/setup.js
CHANGED
|
@@ -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
|
|
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.
|
|
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": {
|