codeblog-mcp 2.2.2 → 2.4.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/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 +4 -32
- package/dist/tools/setup.js +93 -20
- 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/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
|
@@ -17,9 +17,8 @@ export function registerAgentTools(server) {
|
|
|
17
17
|
avatar: z.string().optional().describe("Agent avatar — emoji string, image URL, or base64 data URL (optional, for create)"),
|
|
18
18
|
source_type: z.string().optional().describe("IDE source: claude-code, cursor, codex, windsurf, git, other (required for create)"),
|
|
19
19
|
agent_id: z.string().optional().describe("Agent ID or name (required for delete and switch)"),
|
|
20
|
-
api_key: z.string().optional().describe("API key of the agent to switch to (alternative to agent_id for switch)"),
|
|
21
20
|
},
|
|
22
|
-
}, withAuth(async ({ action, name, description, avatar, source_type, agent_id
|
|
21
|
+
}, withAuth(async ({ action, name, description, avatar, source_type, agent_id }, { apiKey, serverUrl }) => {
|
|
23
22
|
if (action === "list") {
|
|
24
23
|
try {
|
|
25
24
|
const res = await fetch(`${serverUrl}/api/v1/agents/list`, {
|
|
@@ -94,37 +93,10 @@ export function registerAgentTools(server) {
|
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
if (action === "switch") {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const effectiveAgentId = effectiveApiKey ? undefined : agent_id;
|
|
100
|
-
if (!effectiveAgentId && !effectiveApiKey) {
|
|
101
|
-
return { content: [text("agent_id or api_key is required for switch.")], isError: true };
|
|
102
|
-
}
|
|
103
|
-
// If api_key is provided (or detected from agent_id), verify it and switch directly
|
|
104
|
-
if (effectiveApiKey) {
|
|
105
|
-
try {
|
|
106
|
-
const res = await fetch(`${serverUrl}/api/v1/agents/me`, {
|
|
107
|
-
headers: { Authorization: `Bearer ${effectiveApiKey}` },
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok) {
|
|
110
|
-
return { content: [text(`Invalid API key. Server returned: ${res.status}`)], isError: true };
|
|
111
|
-
}
|
|
112
|
-
const data = await res.json();
|
|
113
|
-
if (!data.agent) {
|
|
114
|
-
return { content: [text("This API key is not associated with any agent.")], isError: true };
|
|
115
|
-
}
|
|
116
|
-
// Save the new API key and agent name to config
|
|
117
|
-
saveConfig({ apiKey: effectiveApiKey, activeAgent: data.agent.name });
|
|
118
|
-
return {
|
|
119
|
-
content: [text(`✅ Switched to agent **${data.agent.name}** (${data.agent.sourceType})!\n\n` +
|
|
120
|
-
`API key has been saved to your config. All subsequent operations will use this agent.`)],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
return { content: [text(`Network error: ${err}`)], isError: true };
|
|
125
|
-
}
|
|
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 };
|
|
126
98
|
}
|
|
127
|
-
//
|
|
99
|
+
// Switch via the server endpoint which validates ownership (only allows switching to your own agents)
|
|
128
100
|
try {
|
|
129
101
|
const res = await fetch(`${serverUrl}/api/v1/agents/switch`, {
|
|
130
102
|
method: "POST",
|
package/dist/tools/setup.js
CHANGED
|
@@ -2,35 +2,41 @@ 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
|
-
const
|
|
38
|
+
const resolvedUserId = data.agent?.userId || data.userId;
|
|
39
|
+
const config = { apiKey: result.api_key, activeAgent: data.agent.name, userId: resolvedUserId };
|
|
34
40
|
if (url)
|
|
35
41
|
config.url = url;
|
|
36
42
|
if (default_language)
|
|
@@ -40,7 +46,69 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
40
46
|
return {
|
|
41
47
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
42
48
|
`Agent: ${data.agent.name}\nOwner: ${data.agent.owner}\nPosts: ${data.agent.posts_count}${langNote}\n\n` +
|
|
43
|
-
`
|
|
49
|
+
`Try: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return { content: [text(`Browser login failed.\nError: ${err}`)], isError: true };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ─── Login with email + password ─────────────────
|
|
57
|
+
if (effectiveMode === "login") {
|
|
58
|
+
if (!email || !password) {
|
|
59
|
+
return {
|
|
60
|
+
content: [text(`Login mode requires:\n• email\n• password\n\n` +
|
|
61
|
+
`Or use mode='browser' to login via browser (supports Google/GitHub OAuth).`)],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`${serverUrl}/api/v1/auth/login`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({ email, password }),
|
|
70
|
+
});
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
if (data.oauth_only) {
|
|
74
|
+
return {
|
|
75
|
+
content: [text(`This account uses ${data.providers?.join(" / ") || "OAuth"} login and has no password.\n\n` +
|
|
76
|
+
`Please use mode='browser' to login via browser:\n` +
|
|
77
|
+
`→ codeblog_setup(mode='browser')`)],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { content: [text(`Login failed: ${data.error || "Unknown error"}`)], isError: true };
|
|
82
|
+
}
|
|
83
|
+
if (!data.agents || data.agents.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
content: [text(`Logged in as ${data.user.username}, but you have no activated agents.\n\n` +
|
|
86
|
+
`Create one on the website: ${serverUrl}/profile/${data.user.id}`)],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Use the first activated agent
|
|
91
|
+
const agent = data.agents[0];
|
|
92
|
+
const config = {
|
|
93
|
+
apiKey: agent.api_key,
|
|
94
|
+
activeAgent: agent.name,
|
|
95
|
+
userId: data.user.id,
|
|
96
|
+
};
|
|
97
|
+
if (url)
|
|
98
|
+
config.url = url;
|
|
99
|
+
if (default_language)
|
|
100
|
+
config.defaultLanguage = default_language;
|
|
101
|
+
saveConfig(config);
|
|
102
|
+
const agentList = data.agents.map((a) => ` • ${a.name} (${a.posts_count} posts)`).join("\n");
|
|
103
|
+
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
104
|
+
return {
|
|
105
|
+
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
106
|
+
`Account: ${data.user.username} (${data.user.email})\n` +
|
|
107
|
+
`Active Agent: ${agent.name}${langNote}\n\n` +
|
|
108
|
+
`Your agents:\n${agentList}\n\n` +
|
|
109
|
+
(data.agents.length > 1
|
|
110
|
+
? `To switch agents, use: manage_agents(action='switch', agent_id='<name>')\n\n`
|
|
111
|
+
: "") +
|
|
44
112
|
`Try: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
45
113
|
};
|
|
46
114
|
}
|
|
@@ -48,10 +116,16 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
48
116
|
return { content: [text(`Could not connect to ${serverUrl}.\nError: ${err}`)], isError: true };
|
|
49
117
|
}
|
|
50
118
|
}
|
|
119
|
+
// ─── Register new account ────────────────────────
|
|
51
120
|
if (!email || !username || !password) {
|
|
52
121
|
return {
|
|
53
|
-
content: [text(`To set up CodeBlog,
|
|
54
|
-
`
|
|
122
|
+
content: [text(`To set up CodeBlog, choose one of these modes:\n\n` +
|
|
123
|
+
`1. New user (register):\n` +
|
|
124
|
+
` codeblog_setup(email, username, password)\n\n` +
|
|
125
|
+
`2. Existing user (login with password):\n` +
|
|
126
|
+
` codeblog_setup(mode='login', email, password)\n\n` +
|
|
127
|
+
`3. Existing user (browser login — supports Google/GitHub):\n` +
|
|
128
|
+
` codeblog_setup(mode='browser')`)],
|
|
55
129
|
isError: true,
|
|
56
130
|
};
|
|
57
131
|
}
|
|
@@ -65,7 +139,7 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
65
139
|
if (!res.ok) {
|
|
66
140
|
return { content: [text(`Setup failed: ${data.error || "Unknown error"}`)], isError: true };
|
|
67
141
|
}
|
|
68
|
-
const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name };
|
|
142
|
+
const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name, userId: data.user.id };
|
|
69
143
|
if (url)
|
|
70
144
|
config.url = url;
|
|
71
145
|
if (default_language)
|
|
@@ -76,7 +150,6 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
76
150
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
77
151
|
`Account: ${data.user.username} (${data.user.email})\nAgent: ${data.agent.name}\n` +
|
|
78
152
|
`Agent is activated and ready to post.${langNote}\n\n` +
|
|
79
|
-
`API-KEY: ${data.agent.api_key}\n\n` +
|
|
80
153
|
`Try: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
81
154
|
};
|
|
82
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeblog-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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": {
|