@dalcontak/blogger-mcp-server 1.0.0 → 1.0.2

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/server.js CHANGED
@@ -4,42 +4,32 @@ exports.createToolDefinitions = createToolDefinitions;
4
4
  exports.initMCPServer = initMCPServer;
5
5
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
6
  const zod_1 = require("zod");
7
- /**
8
- * Creates the tool definitions for the Blogger MCP server
9
- * @param bloggerService Blogger service to interact with the API
10
- * @returns Array of tool definitions
11
- */
7
+ function createToolHandler(toolName, fn) {
8
+ return async () => {
9
+ try {
10
+ const result = await fn();
11
+ return {
12
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
13
+ };
14
+ }
15
+ catch (error) {
16
+ console.error(`Error in ${toolName}:`, error);
17
+ return {
18
+ content: [{ type: 'text', text: `Error in ${toolName}: ${error}` }],
19
+ isError: true
20
+ };
21
+ }
22
+ };
23
+ }
12
24
  function createToolDefinitions(bloggerService) {
13
25
  return [
14
26
  {
15
27
  name: 'list_blogs',
16
28
  description: 'Lists all accessible blogs',
17
29
  args: zod_1.z.object({}),
18
- handler: async (_args, _extra) => {
19
- try {
20
- const blogs = await bloggerService.listBlogs();
21
- return {
22
- content: [
23
- {
24
- type: 'text',
25
- text: JSON.stringify({ blogs }, null, 2)
26
- }
27
- ]
28
- };
29
- }
30
- catch (error) {
31
- console.error('Error fetching blogs:', error);
32
- return {
33
- content: [
34
- {
35
- type: 'text',
36
- text: `Error fetching blogs: ${error}`
37
- }
38
- ],
39
- isError: true
40
- };
41
- }
42
- }
30
+ handler: createToolHandler('list_blogs', () => {
31
+ return bloggerService.listBlogs().then(blogs => ({ blogs }));
32
+ })
43
33
  },
44
34
  {
45
35
  name: 'get_blog',
@@ -47,31 +37,9 @@ function createToolDefinitions(bloggerService) {
47
37
  args: zod_1.z.object({
48
38
  blogId: zod_1.z.string().describe('Blog ID')
49
39
  }),
50
- handler: async (args, _extra) => {
51
- try {
52
- const blog = await bloggerService.getBlog(args.blogId);
53
- return {
54
- content: [
55
- {
56
- type: 'text',
57
- text: JSON.stringify({ blog }, null, 2)
58
- }
59
- ]
60
- };
61
- }
62
- catch (error) {
63
- console.error(`Error fetching blog ${args.blogId}:`, error);
64
- return {
65
- content: [
66
- {
67
- type: 'text',
68
- text: `Error fetching blog: ${error}`
69
- }
70
- ],
71
- isError: true
72
- };
73
- }
74
- }
40
+ handler: (args) => createToolHandler('get_blog', () => {
41
+ return bloggerService.getBlog(args.blogId).then(blog => ({ blog }));
42
+ })()
75
43
  },
76
44
  {
77
45
  name: 'get_blog_by_url',
@@ -79,50 +47,9 @@ function createToolDefinitions(bloggerService) {
79
47
  args: zod_1.z.object({
80
48
  url: zod_1.z.string().describe('Blog URL')
81
49
  }),
82
- handler: async (args, _extra) => {
83
- try {
84
- const blog = await bloggerService.getBlogByUrl(args.url);
85
- return {
86
- content: [
87
- {
88
- type: 'text',
89
- text: JSON.stringify({ blog }, null, 2)
90
- }
91
- ]
92
- };
93
- }
94
- catch (error) {
95
- console.error(`Error fetching blog by URL ${args.url}:`, error);
96
- return {
97
- content: [
98
- {
99
- type: 'text',
100
- text: `Error fetching blog by URL: ${error}`
101
- }
102
- ],
103
- isError: true
104
- };
105
- }
106
- }
107
- },
108
- {
109
- name: 'create_blog',
110
- description: 'Creates a new blog (not supported by the Blogger API)',
111
- args: zod_1.z.object({
112
- name: zod_1.z.string().describe('Blog name'),
113
- description: zod_1.z.string().optional().describe('Blog description')
114
- }),
115
- handler: async (_args, _extra) => {
116
- return {
117
- content: [
118
- {
119
- type: 'text',
120
- text: 'Blog creation is not supported by the Blogger API. Please create a blog via the Blogger web interface.'
121
- }
122
- ],
123
- isError: true
124
- };
125
- }
50
+ handler: (args) => createToolHandler('get_blog_by_url', () => {
51
+ return bloggerService.getBlogByUrl(args.url).then(blog => ({ blog }));
52
+ })()
126
53
  },
127
54
  {
128
55
  name: 'list_posts',
@@ -131,31 +58,9 @@ function createToolDefinitions(bloggerService) {
131
58
  blogId: zod_1.z.string().describe('Blog ID'),
132
59
  maxResults: zod_1.z.number().optional().describe('Maximum number of results to return')
133
60
  }),
134
- handler: async (args, _extra) => {
135
- try {
136
- const posts = await bloggerService.listPosts(args.blogId, args.maxResults);
137
- return {
138
- content: [
139
- {
140
- type: 'text',
141
- text: JSON.stringify({ posts }, null, 2)
142
- }
143
- ]
144
- };
145
- }
146
- catch (error) {
147
- console.error(`Error fetching posts for blog ${args.blogId}:`, error);
148
- return {
149
- content: [
150
- {
151
- type: 'text',
152
- text: `Error fetching posts: ${error}`
153
- }
154
- ],
155
- isError: true
156
- };
157
- }
158
- }
61
+ handler: (args) => createToolHandler('list_posts', () => {
62
+ return bloggerService.listPosts(args.blogId, args.maxResults).then(posts => ({ posts }));
63
+ })()
159
64
  },
160
65
  {
161
66
  name: 'search_posts',
@@ -165,31 +70,9 @@ function createToolDefinitions(bloggerService) {
165
70
  query: zod_1.z.string().describe('Search term'),
166
71
  maxResults: zod_1.z.number().optional().describe('Maximum number of results to return')
167
72
  }),
168
- handler: async (args, _extra) => {
169
- try {
170
- const posts = await bloggerService.searchPosts(args.blogId, args.query, args.maxResults);
171
- return {
172
- content: [
173
- {
174
- type: 'text',
175
- text: JSON.stringify({ posts }, null, 2)
176
- }
177
- ]
178
- };
179
- }
180
- catch (error) {
181
- console.error(`Error searching posts in blog ${args.blogId}:`, error);
182
- return {
183
- content: [
184
- {
185
- type: 'text',
186
- text: `Error searching posts: ${error}`
187
- }
188
- ],
189
- isError: true
190
- };
191
- }
192
- }
73
+ handler: (args) => createToolHandler('search_posts', () => {
74
+ return bloggerService.searchPosts(args.blogId, args.query, args.maxResults).then(posts => ({ posts }));
75
+ })()
193
76
  },
194
77
  {
195
78
  name: 'get_post',
@@ -198,31 +81,9 @@ function createToolDefinitions(bloggerService) {
198
81
  blogId: zod_1.z.string().describe('Blog ID'),
199
82
  postId: zod_1.z.string().describe('Post ID')
200
83
  }),
201
- handler: async (args, _extra) => {
202
- try {
203
- const post = await bloggerService.getPost(args.blogId, args.postId);
204
- return {
205
- content: [
206
- {
207
- type: 'text',
208
- text: JSON.stringify({ post }, null, 2)
209
- }
210
- ]
211
- };
212
- }
213
- catch (error) {
214
- console.error(`Error fetching post ${args.postId}:`, error);
215
- return {
216
- content: [
217
- {
218
- type: 'text',
219
- text: `Error fetching post: ${error}`
220
- }
221
- ],
222
- isError: true
223
- };
224
- }
225
- }
84
+ handler: (args) => createToolHandler('get_post', () => {
85
+ return bloggerService.getPost(args.blogId, args.postId).then(post => ({ post }));
86
+ })()
226
87
  },
227
88
  {
228
89
  name: 'create_post',
@@ -233,35 +94,13 @@ function createToolDefinitions(bloggerService) {
233
94
  content: zod_1.z.string().describe('Post content'),
234
95
  labels: zod_1.z.array(zod_1.z.string()).optional().describe('Labels to associate with the post')
235
96
  }),
236
- handler: async (args, _extra) => {
237
- try {
238
- const post = await bloggerService.createPost(args.blogId, {
239
- title: args.title,
240
- content: args.content,
241
- labels: args.labels
242
- });
243
- return {
244
- content: [
245
- {
246
- type: 'text',
247
- text: JSON.stringify({ post }, null, 2)
248
- }
249
- ]
250
- };
251
- }
252
- catch (error) {
253
- console.error(`Error creating post in blog ${args.blogId}:`, error);
254
- return {
255
- content: [
256
- {
257
- type: 'text',
258
- text: `Error creating post: ${error}`
259
- }
260
- ],
261
- isError: true
262
- };
263
- }
264
- }
97
+ handler: (args) => createToolHandler('create_post', () => {
98
+ return bloggerService.createPost(args.blogId, {
99
+ title: args.title,
100
+ content: args.content,
101
+ labels: args.labels
102
+ }).then(post => ({ post }));
103
+ })()
265
104
  },
266
105
  {
267
106
  name: 'update_post',
@@ -273,35 +112,13 @@ function createToolDefinitions(bloggerService) {
273
112
  content: zod_1.z.string().optional().describe('New post content'),
274
113
  labels: zod_1.z.array(zod_1.z.string()).optional().describe('New labels to associate with the post')
275
114
  }),
276
- handler: async (args, _extra) => {
277
- try {
278
- const post = await bloggerService.updatePost(args.blogId, args.postId, {
279
- title: args.title,
280
- content: args.content,
281
- labels: args.labels
282
- });
283
- return {
284
- content: [
285
- {
286
- type: 'text',
287
- text: JSON.stringify({ post }, null, 2)
288
- }
289
- ]
290
- };
291
- }
292
- catch (error) {
293
- console.error(`Error updating post ${args.postId}:`, error);
294
- return {
295
- content: [
296
- {
297
- type: 'text',
298
- text: `Error updating post: ${error}`
299
- }
300
- ],
301
- isError: true
302
- };
303
- }
304
- }
115
+ handler: (args) => createToolHandler('update_post', () => {
116
+ return bloggerService.updatePost(args.blogId, args.postId, {
117
+ title: args.title,
118
+ content: args.content,
119
+ labels: args.labels
120
+ }).then(post => ({ post }));
121
+ })()
305
122
  },
306
123
  {
307
124
  name: 'delete_post',
@@ -310,31 +127,9 @@ function createToolDefinitions(bloggerService) {
310
127
  blogId: zod_1.z.string().describe('Blog ID'),
311
128
  postId: zod_1.z.string().describe('Post ID')
312
129
  }),
313
- handler: async (args, _extra) => {
314
- try {
315
- await bloggerService.deletePost(args.blogId, args.postId);
316
- return {
317
- content: [
318
- {
319
- type: 'text',
320
- text: JSON.stringify({ success: true }, null, 2)
321
- }
322
- ]
323
- };
324
- }
325
- catch (error) {
326
- console.error(`Error deleting post ${args.postId}:`, error);
327
- return {
328
- content: [
329
- {
330
- type: 'text',
331
- text: `Error deleting post: ${error}`
332
- }
333
- ],
334
- isError: true
335
- };
336
- }
337
- }
130
+ handler: (args) => createToolHandler('delete_post', () => {
131
+ return bloggerService.deletePost(args.blogId, args.postId).then(() => ({ success: true }));
132
+ })()
338
133
  },
339
134
  {
340
135
  name: 'list_labels',
@@ -342,31 +137,9 @@ function createToolDefinitions(bloggerService) {
342
137
  args: zod_1.z.object({
343
138
  blogId: zod_1.z.string().describe('Blog ID')
344
139
  }),
345
- handler: async (args, _extra) => {
346
- try {
347
- const labels = await bloggerService.listLabels(args.blogId);
348
- return {
349
- content: [
350
- {
351
- type: 'text',
352
- text: JSON.stringify({ labels }, null, 2)
353
- }
354
- ]
355
- };
356
- }
357
- catch (error) {
358
- console.error(`Error fetching labels for blog ${args.blogId}:`, error);
359
- return {
360
- content: [
361
- {
362
- type: 'text',
363
- text: `Error fetching labels: ${error}`
364
- }
365
- ],
366
- isError: true
367
- };
368
- }
369
- }
140
+ handler: (args) => createToolHandler('list_labels', () => {
141
+ return bloggerService.listLabels(args.blogId).then(labels => ({ labels: labels.items }));
142
+ })()
370
143
  },
371
144
  {
372
145
  name: 'get_label',
@@ -375,74 +148,21 @@ function createToolDefinitions(bloggerService) {
375
148
  blogId: zod_1.z.string().describe('Blog ID'),
376
149
  labelName: zod_1.z.string().describe('Label name')
377
150
  }),
378
- handler: async (args, _extra) => {
379
- try {
380
- const label = await bloggerService.getLabel(args.blogId, args.labelName);
381
- return {
382
- content: [
383
- {
384
- type: 'text',
385
- text: JSON.stringify({ label }, null, 2)
386
- }
387
- ]
388
- };
389
- }
390
- catch (error) {
391
- console.error(`Error fetching label ${args.labelName}:`, error);
392
- return {
393
- content: [
394
- {
395
- type: 'text',
396
- text: `Error fetching label: ${error}`
397
- }
398
- ],
399
- isError: true
400
- };
401
- }
402
- }
151
+ handler: (args) => createToolHandler('get_label', () => {
152
+ return bloggerService.getLabel(args.blogId, args.labelName).then(label => ({ label }));
153
+ })()
403
154
  }
404
155
  ];
405
156
  }
406
- /**
407
- * Initializes the MCP server with all Blogger tools
408
- * @param bloggerService Blogger service to interact with the API
409
- * @param config Server configuration
410
- * @returns MCP server instance
411
- */
412
157
  function initMCPServer(bloggerService, config) {
413
- // Create a new MCP server instance with server information
414
158
  const server = new mcp_js_1.McpServer({
415
159
  name: "Blogger MCP Server",
416
160
  version: "1.0.4",
417
161
  vendor: "mcproadev"
418
162
  });
419
- // Get all tool definitions
420
163
  const toolDefinitions = createToolDefinitions(bloggerService);
421
- // Register each tool with the MCP server
422
164
  for (const tool of toolDefinitions) {
423
- // We can't directly pass the schema object if it's already a Zod object in our definition,
424
- // The MCP SDK expects the shape, not the Zod object itself for the 'args' parameter in server.tool()
425
- // However, looking at the previous code:
426
- // server.tool('name', 'desc', { param: z.string() }, handler)
427
- // The previous code passed an object with Zod schemas as values.
428
- // Our ToolDefinition.args is a z.ZodType<any>, which is likely a z.object({...}).
429
- // We need to extract the shape from the z.object to pass it to server.tool if we want to match the signature.
430
- // Actually, looking at the SDK, server.tool takes:
431
- // name: string, description: string, args: ToolArgs, handler: ToolCallback
432
- // where ToolArgs is Record<string, ZodType<any>>
433
- // So my ToolDefinition.args should probably be Record<string, ZodType<any>> instead of z.ZodType<any>
434
- // to make it easier to spread.
435
- // Let's adjust the implementation in the loop.
436
- // Since I defined args as z.ZodType<any> (which is z.object({...})), I can cast it or access .shape if it's a ZodObject.
437
- if (tool.args instanceof zod_1.z.ZodObject) {
438
- server.tool(tool.name, tool.description, tool.args.shape, tool.handler);
439
- }
440
- else {
441
- // Fallback for empty objects or other schemas if we had them (list_blogs has empty object)
442
- // If it's not a ZodObject, we might have issues if the SDK expects a shape map.
443
- // list_blogs used {} which is compatible with Record<string, ZodType>
444
- server.tool(tool.name, tool.description, {}, tool.handler);
445
- }
165
+ server.tool(tool.name, tool.description, tool.args.shape, tool.handler);
446
166
  }
447
167
  return server;
448
168
  }
package/dist/types.d.ts CHANGED
@@ -1,52 +1,23 @@
1
1
  import { z } from 'zod';
2
- /**
3
- * Types used in the MCP server for Blogger
4
- */
5
- export interface ToolDefinition {
6
- name: string;
7
- description: string;
8
- args: z.ZodType<any>;
9
- handler: (args: any, extra?: any) => Promise<any>;
2
+ import { blogger_v3 } from 'googleapis';
3
+ export interface ToolResult {
4
+ content: Array<{
5
+ type: string;
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
10
9
  }
11
- export interface BloggerBlog {
12
- id: string;
10
+ export interface ToolDefinition<T extends z.ZodRawShape = z.ZodRawShape> {
13
11
  name: string;
14
- description?: string;
15
- url: string;
16
- status?: string;
17
- posts?: BloggerPost[];
18
- labels?: BloggerLabel[];
19
- }
20
- export interface BloggerPost {
21
- id: string;
22
- blogId: string;
23
- title: string;
24
- content: string;
25
- url?: string;
26
- published?: string;
27
- updated?: string;
28
- author?: {
29
- id: string;
30
- displayName: string;
31
- url: string;
32
- image?: {
33
- url: string;
34
- };
35
- };
36
- labels?: string[];
12
+ description: string;
13
+ args: z.ZodObject<T>;
14
+ handler: (args: z.infer<z.ZodObject<T>>) => Promise<ToolResult>;
37
15
  }
38
- export interface BloggerLabel {
39
- id?: string;
16
+ export type BloggerBlog = blogger_v3.Schema$Blog;
17
+ export type BloggerPost = blogger_v3.Schema$Post;
18
+ export type BloggerLabel = {
40
19
  name: string;
41
- }
42
- export interface SearchParams {
43
- query: string;
44
- maxResults?: number;
45
- }
46
- export interface PaginationParams {
47
- pageToken?: string;
48
- maxResults?: number;
49
- }
20
+ };
50
21
  export type ServerMode = {
51
22
  type: 'stdio';
52
23
  } | {
@@ -8,7 +8,6 @@ const express_1 = __importDefault(require("express"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const http_1 = require("http");
10
10
  const socket_io_1 = require("socket.io");
11
- // UI manager implementation
12
11
  class WebUIManager {
13
12
  constructor() {
14
13
  this.server = null;
@@ -28,40 +27,33 @@ class WebUIManager {
28
27
  toolUsage: {}
29
28
  };
30
29
  this.app = (0, express_1.default)();
31
- // Express configuration
32
30
  this.app.use(express_1.default.json());
33
31
  this.app.use(express_1.default.static(path_1.default.join(__dirname, '../public')));
34
- // API routes
35
- this.app.get('/api/status', (req, res) => {
32
+ this.app.get('/api/status', (_req, res) => {
36
33
  res.json(this.status);
37
34
  });
38
- this.app.get('/api/connections', (req, res) => {
35
+ this.app.get('/api/connections', (_req, res) => {
39
36
  res.json(this.connections);
40
37
  });
41
- this.app.get('/api/stats', (req, res) => {
38
+ this.app.get('/api/stats', (_req, res) => {
42
39
  res.json(this.stats);
43
40
  });
44
- // Main route for the UI - wildcard route fix
45
- this.app.get('/', (req, res) => {
41
+ this.app.get('/', (_req, res) => {
46
42
  res.sendFile(path_1.default.join(__dirname, '../public/index.html'));
47
43
  });
48
44
  }
49
45
  async start(port) {
50
- return new Promise((resolve) => {
46
+ return new Promise((resolve, reject) => {
51
47
  this.server = new http_1.Server(this.app);
52
48
  this.io = new socket_io_1.Server(this.server);
53
- // Socket.IO configuration
49
+ this.server.on('error', (err) => {
50
+ reject(err);
51
+ });
54
52
  this.io.on('connection', (socket) => {
55
53
  console.log('New UI connection:', socket.id);
56
- // Send initial data
57
54
  socket.emit('status', this.status);
58
55
  socket.emit('connections', this.connections);
59
56
  socket.emit('stats', this.stats);
60
- // Handle user actions
61
- socket.on('restart-server', () => {
62
- console.log('Server restart request received');
63
- // Restart logic to be implemented
64
- });
65
57
  });
66
58
  this.server.listen(port, () => {
67
59
  console.log(`Web UI started on port ${port}`);
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@dalcontak/blogger-mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "MCP Server for Blogger API",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/dalcontak/blogger-mcp-server.git"
9
+ },
6
10
  "main": "dist/index.js",
7
11
  "types": "dist/index.d.ts",
8
12
  "scripts": {
@@ -361,7 +361,11 @@ describe('write operations (OAuth2)', () => {
361
361
  const result = await service.createPost('blog1', newPost);
362
362
  expect(mockPostsInsert).toHaveBeenCalledWith({
363
363
  blogId: 'blog1',
364
- requestBody: newPost
364
+ requestBody: {
365
+ title: 'New',
366
+ content: '<p>Hello</p>',
367
+ labels: ['test']
368
+ }
365
369
  });
366
370
  expect(result.title).toBe('New');
367
371
  });