@dalcontak/blogger-mcp-server 1.0.1 → 1.0.3

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.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  const config_1 = require("./config");
@@ -6,13 +7,9 @@ const server_1 = require("./server");
6
7
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
7
8
  const http_1 = require("http");
8
9
  const ui_manager_1 = require("./ui-manager");
9
- /**
10
- * Main entry point for the Blogger MCP server
11
- */
12
10
  async function main() {
13
11
  try {
14
12
  console.log('Starting Blogger MCP server...');
15
- // Verify that at least one authentication method is configured
16
13
  const hasOAuth2 = !!(config_1.config.oauth2.clientId && config_1.config.oauth2.clientSecret && config_1.config.oauth2.refreshToken);
17
14
  const hasApiKey = !!config_1.config.blogger.apiKey;
18
15
  if (!hasOAuth2 && !hasApiKey) {
@@ -21,15 +18,8 @@ async function main() {
21
18
  'GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN (full access).');
22
19
  process.exit(1);
23
20
  }
24
- if (hasOAuth2) {
25
- console.log('Authentication mode: OAuth2 (full access)');
26
- }
27
- else {
28
- console.log('Authentication mode: API Key (read-only)');
29
- }
30
- // Initialize the Blogger service
21
+ console.log(`Authentication mode: ${hasOAuth2 ? 'OAuth2 (full access)' : 'API Key (read-only)'}`);
31
22
  const bloggerService = new bloggerService_1.BloggerService();
32
- // Convert configuration to the format expected by the server
33
23
  const serverMode = config_1.config.mode === 'http'
34
24
  ? { type: 'http', host: config_1.config.http.host, port: config_1.config.http.port }
35
25
  : { type: 'stdio' };
@@ -39,24 +29,17 @@ async function main() {
39
29
  oauth2: config_1.config.oauth2,
40
30
  logging: config_1.config.logging
41
31
  };
42
- // Initialize the MCP server with all tools
43
32
  const server = (0, server_1.initMCPServer)(bloggerService, serverConfig);
44
- // Get tool definitions for direct access in HTTP mode and stats
45
33
  const toolDefinitions = (0, server_1.createToolDefinitions)(bloggerService);
46
34
  const toolMap = new Map(toolDefinitions.map(t => [t.name, t]));
47
35
  const serverTools = toolDefinitions.map(t => t.name);
48
- // Initialize the Web UI only if UI_PORT is set
49
36
  let uiManager;
50
37
  let uiPort;
51
- if (process.env.UI_PORT) {
52
- const parsedPort = parseInt(process.env.UI_PORT);
53
- if (!isNaN(parsedPort) && parsedPort > 0 && parsedPort < 65536) {
54
- uiManager = new ui_manager_1.WebUIManager();
55
- uiPort = parsedPort;
56
- await uiManager.start(uiPort);
57
- }
38
+ if (config_1.config.ui.port > 0 && config_1.config.ui.port < 65536) {
39
+ uiManager = new ui_manager_1.WebUIManager();
40
+ uiPort = config_1.config.ui.port;
41
+ await uiManager.start(uiPort);
58
42
  }
59
- // Initialize server statistics and status
60
43
  let serverStatus = {
61
44
  running: true,
62
45
  mode: serverMode.type,
@@ -64,25 +47,77 @@ async function main() {
64
47
  connections: 0,
65
48
  tools: serverTools
66
49
  };
67
- const serverStats = {
50
+ const connections = {};
51
+ const stats = {
68
52
  totalRequests: 0,
69
53
  successfulRequests: 0,
70
- failedRequests: 0,
71
- averageResponseTime: 0,
54
+ totalResponseTime: 0,
72
55
  toolUsage: serverTools.reduce((acc, tool) => {
73
56
  acc[tool] = 0;
74
57
  return acc;
75
58
  }, {})
76
59
  };
60
+ function updateStats(tool, success = true, duration = 0) {
61
+ stats.totalRequests++;
62
+ if (success) {
63
+ stats.successfulRequests++;
64
+ stats.totalResponseTime += duration;
65
+ }
66
+ if (stats.toolUsage[tool] !== undefined) {
67
+ stats.toolUsage[tool]++;
68
+ }
69
+ const updatedStats = {
70
+ totalRequests: stats.totalRequests,
71
+ successfulRequests: stats.successfulRequests,
72
+ failedRequests: stats.totalRequests - stats.successfulRequests,
73
+ averageResponseTime: stats.successfulRequests > 0
74
+ ? Math.round(stats.totalResponseTime / stats.successfulRequests)
75
+ : 0,
76
+ toolUsage: stats.toolUsage
77
+ };
78
+ uiManager?.updateStats(updatedStats);
79
+ }
80
+ function updateConnections(clientId, clientIp) {
81
+ const now = new Date();
82
+ if (!connections[clientId]) {
83
+ connections[clientId] = {
84
+ id: clientId,
85
+ ip: clientIp,
86
+ connectedAt: now,
87
+ lastActivity: now,
88
+ requestCount: 1
89
+ };
90
+ }
91
+ else {
92
+ connections[clientId].lastActivity = now;
93
+ connections[clientId].requestCount++;
94
+ }
95
+ const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
96
+ Object.keys(connections).forEach(id => {
97
+ if (connections[id].lastActivity < fiveMinutesAgo) {
98
+ delete connections[id];
99
+ }
100
+ });
101
+ uiManager?.updateConnections(Object.values(connections));
102
+ serverStatus = {
103
+ ...serverStatus,
104
+ connections: Object.keys(connections).length
105
+ };
106
+ uiManager?.updateStatus(serverStatus);
107
+ }
77
108
  if (uiManager) {
109
+ const initialStats = {
110
+ totalRequests: 0,
111
+ successfulRequests: 0,
112
+ failedRequests: 0,
113
+ averageResponseTime: 0,
114
+ toolUsage: stats.toolUsage
115
+ };
78
116
  uiManager.updateStatus(serverStatus);
79
- uiManager.updateStats(serverStats);
117
+ uiManager.updateStats(initialStats);
80
118
  }
81
- // Configure the appropriate transport based on the mode
82
119
  let httpServer;
83
120
  if (serverMode.type === 'http') {
84
- // For HTTP mode, we use Node.js HTTP server directly
85
- // since the official MCP SDK does not have an HttpServerTransport equivalent
86
121
  const httpMode = serverMode;
87
122
  httpServer = new http_1.Server((req, res) => {
88
123
  if (req.method === 'OPTIONS') {
@@ -101,13 +136,13 @@ async function main() {
101
136
  }
102
137
  let body = '';
103
138
  let bodySize = 0;
104
- const MAX_BODY_SIZE = 1024 * 1024; // 1MB limit
139
+ const MAX_BODY_SIZE = 1024 * 1024;
105
140
  req.on('data', chunk => {
106
141
  bodySize += chunk.length;
107
142
  if (bodySize > MAX_BODY_SIZE) {
108
143
  res.writeHead(413, { 'Content-Type': 'application/json' });
109
144
  res.end(JSON.stringify({ error: 'Request entity too large' }));
110
- req.destroy(); // Stop receiving data
145
+ req.destroy();
111
146
  return;
112
147
  }
113
148
  body += chunk.toString();
@@ -118,73 +153,40 @@ async function main() {
118
153
  try {
119
154
  const request = JSON.parse(body);
120
155
  const { tool, params } = request;
121
- // Add client connection
122
156
  const clientIp = req.socket.remoteAddress || 'unknown';
123
157
  updateConnections(req.socket.remotePort?.toString() || 'client', clientIp);
124
- // Call the appropriate tool
125
158
  try {
126
159
  const startTime = Date.now();
127
160
  const toolDef = toolMap.get(tool);
128
161
  if (!toolDef) {
129
162
  throw new Error(`Unknown tool: ${tool}`);
130
163
  }
131
- // Validate parameters using Zod schema
132
- let validatedParams;
133
- try {
134
- validatedParams = toolDef.args.parse(params || {});
135
- }
136
- catch (validationError) {
137
- throw new Error(`Invalid parameters: ${validationError}`);
138
- }
139
- // Execute tool handler
164
+ const validatedParams = toolDef.args.parse(params || {});
140
165
  const result = await toolDef.handler(validatedParams);
141
166
  const duration = Date.now() - startTime;
142
- // Update success statistics
143
167
  updateStats(tool, true, duration);
144
- // If the handler returned an isError: true, we might want to return 400 or just return the error object
145
- // as per MCP protocol. Here we are in HTTP mode, let's just return 200 with the result object which contains the error message.
146
- // But strictly speaking, if it's an error, we should probably update stats as failed?
147
- // The handler catches exceptions and returns { isError: true, ... }.
148
- // So if result.isError is true, we should count it as failed?
149
- // The previous implementation counted catch block as failed.
150
- // Let's stick to the previous logic: if handler throws, it's a failure. If handler returns result (even error result), it's success execution of the tool.
151
168
  res.writeHead(200, {
152
169
  'Content-Type': 'application/json',
153
170
  'Access-Control-Allow-Origin': '*'
154
171
  });
155
- // MCP Tools return { content: [...] }, but the previous HTTP implementation returned simplified objects like { blogs: [...] }.
156
- // To maintain backward compatibility with the previous HTTP API (if any clients rely on it),
157
- // we might need to transform the MCP result format back to the simplified format?
158
- // The previous switch statement returned `result = { blogs }`.
159
- // The tool handlers now return `{ content: [{ type: 'text', text: JSON.stringify({ blogs }) }] }`.
160
- // We should probably parse the JSON text back if we want to return JSON.
161
- // OR, we just return the MCP result directly.
162
- // Given that this is an MCP server, clients should expect MCP format.
163
- // HOWEVER, the `index.ts` HTTP implementation seemed to be a custom JSON API wrapper around the tools.
164
- // Let's try to parse the response text if possible to match previous behavior,
165
- // OR better: accept that the response format changes to MCP standard or keep it simple.
166
- // The previous implementation was: `res.end(JSON.stringify(result))` where result was `{ blogs: ... }`.
167
- // The tool handlers return `{ content: [{ text: "{\"blogs\":...}" }] }`.
168
- // Let's unwrap it for HTTP mode to keep it friendly, or just return the text.
169
- // If we want to return pure JSON like before:
170
- try {
171
- const textContent = result.content[0].text;
172
- // If the text is JSON, parse it and return that.
173
- const parsedContent = JSON.parse(textContent);
174
- res.end(JSON.stringify(parsedContent));
172
+ const textContent = result.content[0]?.text;
173
+ if (textContent) {
174
+ try {
175
+ const parsedContent = JSON.parse(textContent);
176
+ res.end(JSON.stringify(parsedContent));
177
+ }
178
+ catch {
179
+ res.end(JSON.stringify(result));
180
+ }
175
181
  }
176
- catch (e) {
177
- // If not JSON, return as is wrapped
182
+ else {
178
183
  res.end(JSON.stringify(result));
179
184
  }
180
185
  }
181
186
  catch (error) {
182
- // Update failure statistics
183
187
  updateStats(tool, false);
184
188
  res.writeHead(400, { 'Content-Type': 'application/json' });
185
- res.end(JSON.stringify({
186
- error: `Error executing tool: ${error}`
187
- }));
189
+ res.end(JSON.stringify({ error: `Error executing tool: ${error}` }));
188
190
  }
189
191
  }
190
192
  catch (error) {
@@ -202,94 +204,20 @@ async function main() {
202
204
  });
203
205
  }
204
206
  else {
205
- // For stdio mode, we use the official MCP SDK transport
206
207
  const transport = new stdio_js_1.StdioServerTransport();
207
208
  await server.connect(transport);
208
- console.log(`Blogger MCP server started in stdio mode`);
209
+ console.log('Blogger MCP server started in stdio mode');
209
210
  if (uiPort) {
210
211
  console.log(`Web UI available at http://localhost:${uiPort}`);
211
212
  }
212
213
  }
213
- // Functions to update statistics and connections
214
- const connections = {};
215
- let stats = {
216
- totalRequests: 0,
217
- successfulRequests: 0,
218
- failedRequests: 0,
219
- totalResponseTime: 0,
220
- toolUsage: serverTools.reduce((acc, tool) => {
221
- acc[tool] = 0;
222
- return acc;
223
- }, {})
224
- };
225
- function updateStats(tool, success = true, duration = 0) {
226
- stats.totalRequests++;
227
- if (success) {
228
- stats.successfulRequests++;
229
- stats.totalResponseTime += duration;
230
- }
231
- if (stats.toolUsage[tool] !== undefined) {
232
- stats.toolUsage[tool]++;
233
- }
234
- const updatedStats = {
235
- totalRequests: stats.totalRequests,
236
- successfulRequests: stats.successfulRequests,
237
- failedRequests: stats.totalRequests - stats.successfulRequests,
238
- averageResponseTime: stats.successfulRequests > 0
239
- ? Math.round(stats.totalResponseTime / stats.successfulRequests)
240
- : 0,
241
- toolUsage: stats.toolUsage
242
- };
243
- if (uiManager) {
244
- uiManager.updateStats(updatedStats);
245
- }
246
- }
247
- function updateConnections(clientId, clientIp) {
248
- const now = new Date();
249
- if (!connections[clientId]) {
250
- connections[clientId] = {
251
- id: clientId,
252
- ip: clientIp,
253
- connectedAt: now,
254
- lastActivity: now,
255
- requestCount: 1
256
- };
257
- }
258
- else {
259
- connections[clientId].lastActivity = now;
260
- connections[clientId].requestCount++;
261
- }
262
- // Clean up inactive connections (older than 5 minutes)
263
- const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
264
- Object.keys(connections).forEach(id => {
265
- if (connections[id].lastActivity < fiveMinutesAgo) {
266
- delete connections[id];
267
- }
268
- });
269
- if (uiManager) {
270
- uiManager.updateConnections(Object.values(connections));
271
- }
272
- // Update status with connection count
273
- // FIX: Update the variable and then send it
274
- serverStatus = {
275
- ...serverStatus,
276
- connections: Object.keys(connections).length
277
- };
278
- if (uiManager) {
279
- uiManager.updateStatus(serverStatus);
280
- }
281
- }
282
- // Graceful shutdown
283
214
  const shutdown = async () => {
284
215
  console.log('Shutting down...');
285
216
  serverStatus = { ...serverStatus, running: false };
286
- if (uiManager) {
287
- uiManager.updateStatus(serverStatus);
288
- }
217
+ uiManager?.updateStatus(serverStatus);
289
218
  if (httpServer) {
290
219
  httpServer.close();
291
220
  }
292
- // Allow time for cleanup if needed
293
221
  setTimeout(() => process.exit(0), 1000);
294
222
  };
295
223
  process.on('SIGINT', shutdown);
@@ -300,5 +228,4 @@ async function main() {
300
228
  process.exit(1);
301
229
  }
302
230
  }
303
- // Run main function
304
231
  main();
package/dist/server.d.ts CHANGED
@@ -1,16 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ServerConfig, ToolDefinition } from './types';
3
3
  import { BloggerService } from './bloggerService';
4
- /**
5
- * Creates the tool definitions for the Blogger MCP server
6
- * @param bloggerService Blogger service to interact with the API
7
- * @returns Array of tool definitions
8
- */
9
4
  export declare function createToolDefinitions(bloggerService: BloggerService): ToolDefinition[];
10
- /**
11
- * Initializes the MCP server with all Blogger tools
12
- * @param bloggerService Blogger service to interact with the API
13
- * @param config Server configuration
14
- * @returns MCP server instance
15
- */
16
5
  export declare function initMCPServer(bloggerService: BloggerService, config: ServerConfig): McpServer;