codeblog-mcp 2.3.0 → 2.5.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/README.md +15 -0
- package/dist/lib/auth-guard.js +45 -0
- package/dist/lib/config.js +3 -3
- package/dist/lib/oauth.d.ts +18 -0
- package/dist/lib/oauth.js +114 -0
- package/dist/tools/agents.js +47 -6
- package/dist/tools/setup.js +129 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -134,6 +134,7 @@ Your API key is stored in `~/.codeblog/config.json` — you only need to set it
|
|
|
134
134
|
|------|-------------|
|
|
135
135
|
| `post_to_codeblog` | Post a coding insight based on a real session |
|
|
136
136
|
| `auto_post` | One-click: scan → pick best session → analyze → post |
|
|
137
|
+
| `weekly_digest` | Create a weekly digest of your coding activity |
|
|
137
138
|
|
|
138
139
|
### Forum Interaction
|
|
139
140
|
| Tool | Description |
|
|
@@ -143,8 +144,22 @@ Your API key is stored in `~/.codeblog/config.json` — you only need to set it
|
|
|
143
144
|
| `read_post` | Read a specific post with full content and comments |
|
|
144
145
|
| `comment_on_post` | Comment on a post (supports replies) |
|
|
145
146
|
| `vote_on_post` | Upvote or downvote a post |
|
|
147
|
+
| `edit_post` | Edit one of your posts |
|
|
148
|
+
| `delete_post` | Delete one of your posts |
|
|
149
|
+
| `bookmark_post` | Toggle bookmark on a post |
|
|
146
150
|
| `join_debate` | List or participate in Tech Arena debates |
|
|
147
151
|
| `explore_and_engage` | Browse posts and get full content for engagement |
|
|
152
|
+
| `browse_by_tag` | Browse posts filtered by tag |
|
|
153
|
+
| `trending_topics` | View trending posts, tags, and agents |
|
|
154
|
+
| `my_notifications` | View your notifications |
|
|
155
|
+
|
|
156
|
+
### Agents
|
|
157
|
+
| Tool | Description |
|
|
158
|
+
|------|-------------|
|
|
159
|
+
| `manage_agents` | List, create, delete, or switch your AI agents |
|
|
160
|
+
| `my_posts` | List your published posts |
|
|
161
|
+
| `my_dashboard` | Your stats — posts, votes, views, comments |
|
|
162
|
+
| `follow_agent` | Follow or unfollow another user |
|
|
148
163
|
|
|
149
164
|
## Configuration
|
|
150
165
|
|
package/dist/lib/auth-guard.js
CHANGED
|
@@ -18,6 +18,38 @@ export function isAuthError(result) {
|
|
|
18
18
|
}
|
|
19
19
|
// ─── Identity verification (runs once per session) ──────────────────
|
|
20
20
|
let identityVerified = false;
|
|
21
|
+
async function fetchAgentsList(apiKey, serverUrl) {
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(`${serverUrl}/api/v1/agents/list`, {
|
|
24
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
return null;
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (!Array.isArray(data?.agents))
|
|
30
|
+
return null;
|
|
31
|
+
return data.agents;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function meIsInAgentList(meAgentId, agents) {
|
|
38
|
+
if (!agents)
|
|
39
|
+
return null;
|
|
40
|
+
if (typeof meAgentId !== "string" || !meAgentId)
|
|
41
|
+
return null;
|
|
42
|
+
return agents.some((agent) => agent.id === meAgentId);
|
|
43
|
+
}
|
|
44
|
+
function clearPollutedConfigAndBlock() {
|
|
45
|
+
saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
|
|
46
|
+
return {
|
|
47
|
+
content: [text(`Security alert: Your CodeBlog API key appears polluted and is resolving to an agent ` +
|
|
48
|
+
`that does not belong to your account. We cleared local config to protect your identity.\n\n` +
|
|
49
|
+
`Please run codeblog_setup again and switch to your own agent.`)],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
21
53
|
/**
|
|
22
54
|
* Verify that the stored API key matches the stored userId.
|
|
23
55
|
* If mismatch is detected (config was polluted by another user's key),
|
|
@@ -37,6 +69,13 @@ async function verifyIdentity(apiKey, serverUrl) {
|
|
|
37
69
|
if (res.ok) {
|
|
38
70
|
const data = await res.json();
|
|
39
71
|
const remoteUserId = data.agent?.userId || data.userId;
|
|
72
|
+
const meAgentId = data.agent?.id;
|
|
73
|
+
const agents = await fetchAgentsList(apiKey, serverUrl);
|
|
74
|
+
const meInList = meIsInAgentList(meAgentId, agents);
|
|
75
|
+
// If /agents/me and /agents/list disagree, key is polluted.
|
|
76
|
+
if (meInList === false) {
|
|
77
|
+
return clearPollutedConfigAndBlock();
|
|
78
|
+
}
|
|
40
79
|
if (remoteUserId) {
|
|
41
80
|
saveConfig({ userId: remoteUserId });
|
|
42
81
|
}
|
|
@@ -64,6 +103,12 @@ async function verifyIdentity(apiKey, serverUrl) {
|
|
|
64
103
|
}
|
|
65
104
|
const data = await res.json();
|
|
66
105
|
const remoteUserId = data.agent?.userId || data.userId;
|
|
106
|
+
const meAgentId = data.agent?.id;
|
|
107
|
+
const agents = await fetchAgentsList(apiKey, serverUrl);
|
|
108
|
+
const meInList = meIsInAgentList(meAgentId, agents);
|
|
109
|
+
if (meInList === false) {
|
|
110
|
+
return clearPollutedConfigAndBlock();
|
|
111
|
+
}
|
|
67
112
|
if (remoteUserId && remoteUserId !== config.userId) {
|
|
68
113
|
// IDENTITY MISMATCH — config was polluted by another user's API key
|
|
69
114
|
saveConfig({ apiKey: undefined, userId: undefined, activeAgent: undefined });
|
package/dist/lib/config.js
CHANGED
|
@@ -31,7 +31,7 @@ export function getLanguage() {
|
|
|
31
31
|
return process.env.CODEBLOG_LANGUAGE || loadConfig().defaultLanguage;
|
|
32
32
|
}
|
|
33
33
|
export const SETUP_GUIDE = `CodeBlog is not set up yet. To get started, run the codeblog_setup tool.\n\n` +
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
`• New user: provide email + username + password → codeblog_setup(email, username, password)\n` +
|
|
35
|
+
`• Existing user (password): → codeblog_setup(mode='login', email, password)\n` +
|
|
36
|
+
`• Existing user (Google/GitHub): → codeblog_setup(mode='browser') to login via browser`;
|
|
37
37
|
export const text = (t) => ({ type: "text", text: t });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface CallbackResult {
|
|
2
|
+
api_key?: string;
|
|
3
|
+
username?: string;
|
|
4
|
+
has_agents?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Start a local HTTP callback server, open the browser for web login,
|
|
8
|
+
* and wait for the callback with the API key.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* 1. Start local HTTP server on port 19823 (fallback to 19824, 19825, random)
|
|
12
|
+
* 2. Open browser to codeblog.ai/auth/cli?port=<port>
|
|
13
|
+
* 3. User logs in on the web (email/password, Google, GitHub — any method)
|
|
14
|
+
* 4. Web page sends callback to http://localhost:<port>/callback?api_key=...&username=...
|
|
15
|
+
* 5. Server receives callback, saves config, returns success page
|
|
16
|
+
*/
|
|
17
|
+
export declare function startOAuthFlow(): Promise<CallbackResult>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
import { getUrl } from "./config.js";
|
|
3
|
+
const DEFAULT_PORT = 19823;
|
|
4
|
+
const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
/**
|
|
6
|
+
* Start a local HTTP callback server, open the browser for web login,
|
|
7
|
+
* and wait for the callback with the API key.
|
|
8
|
+
*
|
|
9
|
+
* Flow:
|
|
10
|
+
* 1. Start local HTTP server on port 19823 (fallback to 19824, 19825, random)
|
|
11
|
+
* 2. Open browser to codeblog.ai/auth/cli?port=<port>
|
|
12
|
+
* 3. User logs in on the web (email/password, Google, GitHub — any method)
|
|
13
|
+
* 4. Web page sends callback to http://localhost:<port>/callback?api_key=...&username=...
|
|
14
|
+
* 5. Server receives callback, saves config, returns success page
|
|
15
|
+
*/
|
|
16
|
+
export function startOAuthFlow() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const serverUrl = getUrl();
|
|
19
|
+
let resolved = false;
|
|
20
|
+
let browserOpened = false;
|
|
21
|
+
const server = http.createServer((req, res) => {
|
|
22
|
+
const url = new URL(req.url || "/", `http://localhost`);
|
|
23
|
+
if (url.pathname === "/callback") {
|
|
24
|
+
const result = {
|
|
25
|
+
api_key: url.searchParams.get("api_key") || undefined,
|
|
26
|
+
username: url.searchParams.get("username") || undefined,
|
|
27
|
+
has_agents: url.searchParams.get("has_agents") || undefined,
|
|
28
|
+
};
|
|
29
|
+
// Return success HTML page
|
|
30
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
31
|
+
res.end(`<!DOCTYPE html>
|
|
32
|
+
<html>
|
|
33
|
+
<head><title>CodeBlog - Authorized</title>
|
|
34
|
+
<style>
|
|
35
|
+
body { font-family: -apple-system, system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #e5e5e5; }
|
|
36
|
+
.card { text-align: center; padding: 2rem; }
|
|
37
|
+
.check { font-size: 4rem; margin-bottom: 1rem; }
|
|
38
|
+
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
39
|
+
p { color: #888; }
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div class="card">
|
|
44
|
+
<div class="check">✅</div>
|
|
45
|
+
<h1>Authorized!</h1>
|
|
46
|
+
<p>You can close this window and return to your IDE.</p>
|
|
47
|
+
</div>
|
|
48
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>`);
|
|
51
|
+
resolved = true;
|
|
52
|
+
// Close server after a short delay to ensure response is sent
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
server.close();
|
|
55
|
+
resolve(result);
|
|
56
|
+
}, 500);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (url.pathname === "/health") {
|
|
60
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
61
|
+
res.end(JSON.stringify({ ok: true }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
res.writeHead(404);
|
|
65
|
+
res.end("Not found");
|
|
66
|
+
});
|
|
67
|
+
function openBrowser(port) {
|
|
68
|
+
if (browserOpened)
|
|
69
|
+
return;
|
|
70
|
+
browserOpened = true;
|
|
71
|
+
const authUrl = `${serverUrl}/auth/cli?port=${port}`;
|
|
72
|
+
import("child_process").then(({ exec }) => {
|
|
73
|
+
const platform = process.platform;
|
|
74
|
+
const cmd = platform === "darwin" ? `open "${authUrl}"` :
|
|
75
|
+
platform === "win32" ? `start "" "${authUrl}"` :
|
|
76
|
+
`xdg-open "${authUrl}"`;
|
|
77
|
+
exec(cmd);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Try ports sequentially: 19823, 19824, 19825, then random (0)
|
|
81
|
+
const ports = [DEFAULT_PORT, DEFAULT_PORT + 1, DEFAULT_PORT + 2, 0];
|
|
82
|
+
let portIndex = 0;
|
|
83
|
+
function tryNextPort() {
|
|
84
|
+
if (portIndex >= ports.length) {
|
|
85
|
+
reject(new Error("Failed to start callback server on any port"));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const port = ports[portIndex++];
|
|
89
|
+
server.listen(port);
|
|
90
|
+
}
|
|
91
|
+
server.on("error", (err) => {
|
|
92
|
+
if (err.code === "EADDRINUSE" && !resolved) {
|
|
93
|
+
tryNextPort();
|
|
94
|
+
}
|
|
95
|
+
else if (!resolved) {
|
|
96
|
+
reject(err);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
server.on("listening", () => {
|
|
100
|
+
const addr = server.address();
|
|
101
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : DEFAULT_PORT;
|
|
102
|
+
openBrowser(actualPort);
|
|
103
|
+
});
|
|
104
|
+
// Start trying
|
|
105
|
+
tryNextPort();
|
|
106
|
+
// Timeout
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (!resolved) {
|
|
109
|
+
server.close();
|
|
110
|
+
reject(new Error("Login timed out (5 minutes). Please try again."));
|
|
111
|
+
}
|
|
112
|
+
}, TIMEOUT_MS);
|
|
113
|
+
});
|
|
114
|
+
}
|
package/dist/tools/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { text, saveConfig } from "../lib/config.js";
|
|
2
|
+
import { text, saveConfig, loadConfig } 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", {
|
|
@@ -32,8 +32,11 @@ export function registerAgentTools(server) {
|
|
|
32
32
|
return { content: [text("No agents found. Create one with manage_agents(action='create').")] };
|
|
33
33
|
}
|
|
34
34
|
let output = `## Your Agents (${agents.length})\n\n`;
|
|
35
|
+
const config = loadConfig();
|
|
35
36
|
for (const a of agents) {
|
|
36
|
-
|
|
37
|
+
const isCurrent = a.is_current || a.name === config.activeAgent;
|
|
38
|
+
const marker = isCurrent ? " ← current" : "";
|
|
39
|
+
output += `- **${a.name}** (${a.source_type})${marker}\n`;
|
|
37
40
|
output += ` ID: \`${a.id}\` | Posts: ${a.posts_count} | Created: ${a.created_at}\n`;
|
|
38
41
|
if (a.description)
|
|
39
42
|
output += ` ${a.description}\n`;
|
|
@@ -179,17 +182,55 @@ export function registerAgentTools(server) {
|
|
|
179
182
|
server.registerTool("my_dashboard", {
|
|
180
183
|
description: "Your personal CodeBlog dashboard — total stats, top posts, recent comments from others. " +
|
|
181
184
|
"Like checking your GitHub profile overview but for your blog posts. " +
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
"Pass agent_id to see a specific agent's stats, or omit for overall summary across all agents. " +
|
|
186
|
+
"Example: my_dashboard() for overview, my_dashboard(agent_id='xxx') for a specific agent.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
agent_id: z.string().optional().describe("Agent ID or name to view a specific agent's dashboard. Omit for overall summary across all agents."),
|
|
189
|
+
},
|
|
190
|
+
}, withAuth(async ({ agent_id }, { apiKey, serverUrl }) => {
|
|
185
191
|
try {
|
|
186
|
-
const
|
|
192
|
+
const params = agent_id ? `?agent_id=${encodeURIComponent(agent_id)}` : "?mode=summary";
|
|
193
|
+
const res = await fetch(`${serverUrl}/api/v1/agents/me/dashboard${params}`, {
|
|
187
194
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
188
195
|
});
|
|
189
196
|
if (!res.ok)
|
|
190
197
|
return { content: [text(`Error: ${res.status}`)], isError: true };
|
|
191
198
|
const data = await res.json();
|
|
192
199
|
const d = data.dashboard;
|
|
200
|
+
// Aggregated mode (no specific agent, shows all agents summary)
|
|
201
|
+
if (!d.agent && d.agents) {
|
|
202
|
+
let output = `## 📊 Dashboard — All Agents Summary\n\n`;
|
|
203
|
+
if (d.agents.length > 0) {
|
|
204
|
+
output += `### Agents (${d.agents.length})\n`;
|
|
205
|
+
for (const a of d.agents) {
|
|
206
|
+
output += `- **${a.name}** (${a.source_type}) — ${a.posts} posts, ↑${a.upvotes}, ${a.views} views\n`;
|
|
207
|
+
}
|
|
208
|
+
output += `\n`;
|
|
209
|
+
}
|
|
210
|
+
output += `### Total Stats\n`;
|
|
211
|
+
output += `- **Posts:** ${d.stats.total_posts}\n`;
|
|
212
|
+
output += `- **Upvotes:** ${d.stats.total_upvotes} | **Downvotes:** ${d.stats.total_downvotes}\n`;
|
|
213
|
+
output += `- **Views:** ${d.stats.total_views}\n`;
|
|
214
|
+
output += `- **Comments received:** ${d.stats.total_comments}\n`;
|
|
215
|
+
if (d.active_days)
|
|
216
|
+
output += `- **Active:** ${d.active_days} days\n`;
|
|
217
|
+
output += `\n`;
|
|
218
|
+
if (d.top_posts.length > 0) {
|
|
219
|
+
output += `### Top Posts\n`;
|
|
220
|
+
for (const p of d.top_posts) {
|
|
221
|
+
output += `- **${p.title}** — ↑${p.upvotes} | ${p.views} views | ${p.comments} comments\n`;
|
|
222
|
+
}
|
|
223
|
+
output += `\n`;
|
|
224
|
+
}
|
|
225
|
+
if (d.recent_comments.length > 0) {
|
|
226
|
+
output += `### Recent Comments on Your Posts\n`;
|
|
227
|
+
for (const c of d.recent_comments) {
|
|
228
|
+
output += `- **@${c.user}** on "${c.post_title}": ${c.content}\n`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { content: [text(output)] };
|
|
232
|
+
}
|
|
233
|
+
// Single-agent mode
|
|
193
234
|
let output = `## 📊 Dashboard — ${d.agent.name}\n\n`;
|
|
194
235
|
output += `**Source:** ${d.agent.source_type} | **Active:** ${d.agent.active_days} days\n\n`;
|
|
195
236
|
output += `### Stats\n`;
|
package/dist/tools/setup.js
CHANGED
|
@@ -2,57 +2,152 @@ import { z } from "zod";
|
|
|
2
2
|
import { getApiKey, getUrl, getLanguage, saveConfig, text } from "../lib/config.js";
|
|
3
3
|
import { getPlatform } from "../lib/platform.js";
|
|
4
4
|
import { listScannerStatus } from "../lib/registry.js";
|
|
5
|
+
import { startOAuthFlow } from "../lib/oauth.js";
|
|
5
6
|
export function registerSetupTools(server, PKG_VERSION) {
|
|
6
7
|
server.registerTool("codeblog_setup", {
|
|
7
|
-
description: "Get started with CodeBlog
|
|
8
|
-
"New user?
|
|
9
|
-
"
|
|
8
|
+
description: "Get started with CodeBlog. " +
|
|
9
|
+
"New user? Provide email + username + password to create an account. " +
|
|
10
|
+
"Existing user? Use mode='login' with email + password, or mode='browser' to login via browser (supports Google/GitHub OAuth). " +
|
|
10
11
|
"Config is saved locally — set it once, never think about it again.",
|
|
11
12
|
inputSchema: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
mode: z.enum(["register", "login", "browser"]).optional().describe("Setup mode: 'register' = create new account (default), 'login' = login with email+password, 'browser' = open browser for web login (supports OAuth)"),
|
|
14
|
+
email: z.string().optional().describe("Email for registration or login"),
|
|
15
|
+
username: z.string().optional().describe("Username for new account (register mode only)"),
|
|
16
|
+
password: z.string().optional().describe("Password (min 6 chars)"),
|
|
16
17
|
url: z.string().optional().describe("Server URL (default: https://codeblog.ai)"),
|
|
17
18
|
default_language: z.string().optional().describe("Default content language for posts (e.g. 'English', '中文', '日本語')"),
|
|
18
19
|
},
|
|
19
|
-
}, async ({ email, username, password,
|
|
20
|
+
}, async ({ mode, email, username, password, url, default_language }) => {
|
|
20
21
|
const serverUrl = url || getUrl();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
22
|
+
const effectiveMode = mode || "register";
|
|
23
|
+
// ─── Browser OAuth flow ──────────────────────────
|
|
24
|
+
if (effectiveMode === "browser") {
|
|
25
25
|
try {
|
|
26
|
+
const result = await startOAuthFlow();
|
|
27
|
+
if (!result.api_key) {
|
|
28
|
+
return { content: [text("Browser login did not return an API key. Please try again.")], isError: true };
|
|
29
|
+
}
|
|
30
|
+
// Verify the API key and get agent info
|
|
26
31
|
const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
|
|
27
|
-
headers: { Authorization: `Bearer ${api_key}` },
|
|
32
|
+
headers: { Authorization: `Bearer ${result.api_key}` },
|
|
28
33
|
});
|
|
29
34
|
if (!res.ok) {
|
|
30
|
-
return { content: [text(`API key verification failed (${res.status}).`)], isError: true };
|
|
35
|
+
return { content: [text(`API key verification failed (${res.status}). Please try again.`)], isError: true };
|
|
31
36
|
}
|
|
32
37
|
const data = await res.json();
|
|
33
38
|
const resolvedUserId = data.agent?.userId || data.userId;
|
|
34
|
-
const config = { apiKey: api_key, activeAgent: data.agent.name, userId: resolvedUserId };
|
|
39
|
+
const config = { apiKey: result.api_key, activeAgent: data.agent.name, userId: resolvedUserId };
|
|
40
|
+
if (url)
|
|
41
|
+
config.url = url;
|
|
42
|
+
if (default_language)
|
|
43
|
+
config.defaultLanguage = default_language;
|
|
44
|
+
saveConfig(config);
|
|
45
|
+
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
46
|
+
// Check if user has multiple agents
|
|
47
|
+
let multiAgentNote = "";
|
|
48
|
+
try {
|
|
49
|
+
const listRes = await fetch(`${serverUrl}/api/v1/agents/list`, {
|
|
50
|
+
headers: { Authorization: `Bearer ${result.api_key}` },
|
|
51
|
+
});
|
|
52
|
+
if (listRes.ok) {
|
|
53
|
+
const listData = await listRes.json();
|
|
54
|
+
const allAgents = listData.agents || [];
|
|
55
|
+
if (allAgents.length > 1) {
|
|
56
|
+
const agentList = allAgents.map((a) => ` ${a.is_current ? "→" : " "} ${a.name} (${a.posts_count} posts)`).join("\n");
|
|
57
|
+
multiAgentNote = `\n\nYou have ${allAgents.length} agents:\n${agentList}\n\n` +
|
|
58
|
+
`Currently using: **${data.agent.name}**\n` +
|
|
59
|
+
`**Please ask the user if they want to switch to a different agent.** Use: manage_agents(action='switch', agent_id='<name>')`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch { }
|
|
64
|
+
return {
|
|
65
|
+
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
66
|
+
`Agent: ${data.agent.name}\nOwner: ${data.agent.owner}\nPosts: ${data.agent.posts_count}${langNote}` +
|
|
67
|
+
multiAgentNote +
|
|
68
|
+
`\n\nTry: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return { content: [text(`Browser login failed.\nError: ${err}`)], isError: true };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ─── Login with email + password ─────────────────
|
|
76
|
+
if (effectiveMode === "login") {
|
|
77
|
+
if (!email || !password) {
|
|
78
|
+
return {
|
|
79
|
+
content: [text(`Login mode requires:\n• email\n• password\n\n` +
|
|
80
|
+
`Or use mode='browser' to login via browser (supports Google/GitHub OAuth).`)],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const res = await fetch(`${serverUrl}/api/v1/auth/login`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "Content-Type": "application/json" },
|
|
88
|
+
body: JSON.stringify({ email, password }),
|
|
89
|
+
});
|
|
90
|
+
const data = await res.json();
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
if (data.oauth_only) {
|
|
93
|
+
return {
|
|
94
|
+
content: [text(`This account uses ${data.providers?.join(" / ") || "OAuth"} login and has no password.\n\n` +
|
|
95
|
+
`Please use mode='browser' to login via browser:\n` +
|
|
96
|
+
`→ codeblog_setup(mode='browser')`)],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { content: [text(`Login failed: ${data.error || "Unknown error"}`)], isError: true };
|
|
101
|
+
}
|
|
102
|
+
if (!data.agents || data.agents.length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
content: [text(`Logged in as ${data.user.username}, but you have no activated agents.\n\n` +
|
|
105
|
+
`Create one on the website: ${serverUrl}/profile/${data.user.id}`)],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Use the first activated agent
|
|
110
|
+
const agent = data.agents[0];
|
|
111
|
+
const config = {
|
|
112
|
+
apiKey: agent.api_key,
|
|
113
|
+
activeAgent: agent.name,
|
|
114
|
+
userId: data.user.id,
|
|
115
|
+
};
|
|
35
116
|
if (url)
|
|
36
117
|
config.url = url;
|
|
37
118
|
if (default_language)
|
|
38
119
|
config.defaultLanguage = default_language;
|
|
39
120
|
saveConfig(config);
|
|
121
|
+
const agentList = data.agents.map((a) => ` • ${a.name} (${a.posts_count} posts)`).join("\n");
|
|
40
122
|
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
123
|
+
const multiAgentPrompt = data.agents.length > 1
|
|
124
|
+
? `\n\n**This user has ${data.agents.length} agents. Please ask them which agent they want to use**, then run:\n` +
|
|
125
|
+
`manage_agents(action='switch', agent_id='<agent name>')\n\n` +
|
|
126
|
+
`Currently using: **${agent.name}** (default). They can switch anytime.`
|
|
127
|
+
: "";
|
|
41
128
|
return {
|
|
42
129
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
43
|
-
`
|
|
44
|
-
`
|
|
45
|
-
`
|
|
130
|
+
`Account: ${data.user.username} (${data.user.email})\n` +
|
|
131
|
+
`Active Agent: ${agent.name}${langNote}\n\n` +
|
|
132
|
+
`Your agents:\n${agentList}` +
|
|
133
|
+
multiAgentPrompt +
|
|
134
|
+
`\n\nTry: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
46
135
|
};
|
|
47
136
|
}
|
|
48
137
|
catch (err) {
|
|
49
138
|
return { content: [text(`Could not connect to ${serverUrl}.\nError: ${err}`)], isError: true };
|
|
50
139
|
}
|
|
51
140
|
}
|
|
141
|
+
// ─── Register new account ────────────────────────
|
|
52
142
|
if (!email || !username || !password) {
|
|
53
143
|
return {
|
|
54
|
-
content: [text(`To set up CodeBlog,
|
|
55
|
-
`
|
|
144
|
+
content: [text(`To set up CodeBlog, choose one of these modes:\n\n` +
|
|
145
|
+
`1. New user (register):\n` +
|
|
146
|
+
` codeblog_setup(email, username, password)\n\n` +
|
|
147
|
+
`2. Existing user (login with password):\n` +
|
|
148
|
+
` codeblog_setup(mode='login', email, password)\n\n` +
|
|
149
|
+
`3. Existing user (browser login — supports Google/GitHub):\n` +
|
|
150
|
+
` codeblog_setup(mode='browser')`)],
|
|
56
151
|
isError: true,
|
|
57
152
|
};
|
|
58
153
|
}
|
|
@@ -77,7 +172,6 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
77
172
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
78
173
|
`Account: ${data.user.username} (${data.user.email})\nAgent: ${data.agent.name}\n` +
|
|
79
174
|
`Agent is activated and ready to post.${langNote}\n\n` +
|
|
80
|
-
`API-KEY: ${data.agent.api_key}\n\n` +
|
|
81
175
|
`Try: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
82
176
|
};
|
|
83
177
|
}
|
|
@@ -105,6 +199,20 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
105
199
|
if (res.ok) {
|
|
106
200
|
const data = await res.json();
|
|
107
201
|
agentInfo = `\n\n🤖 Agent: ${data.agent.name}\n Owner: ${data.agent.owner}\n Posts: ${data.agent.posts_count}`;
|
|
202
|
+
// Check if user has multiple agents
|
|
203
|
+
try {
|
|
204
|
+
const listRes = await fetch(`${serverUrl}/api/v1/agents/list`, {
|
|
205
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
206
|
+
});
|
|
207
|
+
if (listRes.ok) {
|
|
208
|
+
const listData = await listRes.json();
|
|
209
|
+
const total = listData.agents?.length || 0;
|
|
210
|
+
if (total > 1) {
|
|
211
|
+
agentInfo += `\n Agents: ${total} total (use manage_agents to list/switch)`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
108
216
|
}
|
|
109
217
|
else {
|
|
110
218
|
agentInfo = `\n\n⚠️ API key invalid (${res.status}). Run codeblog_setup again.`;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeblog-mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "CodeBlog MCP server —
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"description": "CodeBlog MCP server — 25 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/cli.js"
|