@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.
@@ -1,121 +1,28 @@
1
1
  import { blogger_v3 } from 'googleapis';
2
- import { BloggerBlog, BloggerPost, BloggerLabel } from './types';
3
- /**
4
- * Custom types to compensate for Blogger API limitations
5
- */
2
+ import { BloggerPost } from './types';
6
3
  interface BloggerLabelList {
7
4
  kind?: string;
8
- items?: BloggerLabel[];
5
+ items?: Array<{
6
+ name: string;
7
+ }>;
9
8
  }
10
- /**
11
- * Google Blogger API interaction service
12
- *
13
- * Supports two authentication modes:
14
- * - OAuth2 (GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN):
15
- * full access (read + write). Required for listBlogs, createPost, updatePost, deletePost.
16
- * - API Key (BLOGGER_API_KEY): read-only access to public blogs.
17
- * Works for getBlog, listPosts, getPost, searchPosts, listLabels, getLabel.
18
- *
19
- * If both are configured, OAuth2 is used (it covers all operations).
20
- */
21
9
  export declare class BloggerService {
22
10
  private blogger;
23
11
  private readonly isOAuth2;
24
- /**
25
- * Initializes the Blogger service with OAuth2 or API key
26
- */
27
12
  constructor();
28
- /**
29
- * Checks that OAuth2 authentication is available.
30
- * Throws an explicit error if the operation requires OAuth2 and we are in API key mode.
31
- */
32
13
  private requireOAuth2;
33
- /**
34
- * Lists all blogs for the authenticated user.
35
- * Requires OAuth2 (blogs.listByUser with userId: 'self').
36
- * @returns Blog list
37
- */
38
14
  listBlogs(): Promise<blogger_v3.Schema$BlogList>;
39
- /**
40
- * Retrieves details of a specific blog
41
- * @param blogId ID of the blog to retrieve
42
- * @returns Blog details
43
- */
44
15
  getBlog(blogId: string): Promise<blogger_v3.Schema$Blog>;
45
- /**
46
- * Retrieves a blog by its URL
47
- * @param url Blog URL
48
- * @returns Blog details
49
- */
50
16
  getBlogByUrl(url: string): Promise<blogger_v3.Schema$Blog>;
51
- /**
52
- * Simulates blog creation.
53
- * Note: The Blogger API does not actually allow creating a blog via API.
54
- * This method simulates the functionality and returns an explanatory error message.
55
- *
56
- * @param blogData Blog data to create
57
- * @returns Explanatory error message
58
- */
59
- createBlog(blogData: Partial<BloggerBlog>): Promise<any>;
60
- /**
61
- * Lists posts from a blog
62
- * @param blogId Blog ID
63
- * @param maxResults Maximum number of results to return
64
- * @returns Post list
65
- */
66
17
  listPosts(blogId: string, maxResults?: number): Promise<blogger_v3.Schema$PostList>;
67
- /**
68
- * Searches posts in a blog using the native posts.search endpoint of the Blogger API
69
- * @param blogId Blog ID
70
- * @param query Search term
71
- * @param maxResults Maximum number of results to return
72
- * @returns List of matching posts
73
- */
74
18
  searchPosts(blogId: string, query: string, maxResults?: number): Promise<blogger_v3.Schema$PostList>;
75
- /**
76
- * Retrieves a specific post
77
- * @param blogId Blog ID
78
- * @param postId Post ID
79
- * @returns Post details
80
- */
81
19
  getPost(blogId: string, postId: string): Promise<blogger_v3.Schema$Post>;
82
- /**
83
- * Creates a new post in a blog.
84
- * Requires OAuth2.
85
- * @param blogId Blog ID
86
- * @param postData Post data to create
87
- * @returns Created post
88
- */
89
20
  createPost(blogId: string, postData: Partial<BloggerPost>): Promise<blogger_v3.Schema$Post>;
90
- /**
91
- * Updates an existing post.
92
- * Requires OAuth2.
93
- * @param blogId Blog ID
94
- * @param postId Post ID
95
- * @param postData Post data to update
96
- * @returns Updated post
97
- */
98
21
  updatePost(blogId: string, postId: string, postData: Partial<BloggerPost>): Promise<blogger_v3.Schema$Post>;
99
- /**
100
- * Deletes a post.
101
- * Requires OAuth2.
102
- * @param blogId Blog ID
103
- * @param postId Post ID
104
- * @returns Deletion status
105
- */
106
22
  deletePost(blogId: string, postId: string): Promise<void>;
107
- /**
108
- * Lists labels from a blog
109
- * @param blogId Blog ID
110
- * @returns Label list
111
- */
112
23
  listLabels(blogId: string): Promise<BloggerLabelList>;
113
- /**
114
- * Retrieves a specific label
115
- * @param blogId Blog ID
116
- * @param labelName Label name
117
- * @returns Label details
118
- */
119
- getLabel(blogId: string, labelName: string): Promise<BloggerLabel>;
24
+ getLabel(blogId: string, labelName: string): Promise<{
25
+ name: string;
26
+ }>;
120
27
  }
121
28
  export {};
@@ -3,21 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BloggerService = void 0;
4
4
  const googleapis_1 = require("googleapis");
5
5
  const config_1 = require("./config");
6
- /**
7
- * Google Blogger API interaction service
8
- *
9
- * Supports two authentication modes:
10
- * - OAuth2 (GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN):
11
- * full access (read + write). Required for listBlogs, createPost, updatePost, deletePost.
12
- * - API Key (BLOGGER_API_KEY): read-only access to public blogs.
13
- * Works for getBlog, listPosts, getPost, searchPosts, listLabels, getLabel.
14
- *
15
- * If both are configured, OAuth2 is used (it covers all operations).
16
- */
17
6
  class BloggerService {
18
- /**
19
- * Initializes the Blogger service with OAuth2 or API key
20
- */
21
7
  constructor() {
22
8
  const { oauth2 } = config_1.config;
23
9
  const hasOAuth2 = !!(oauth2.clientId && oauth2.clientSecret && oauth2.refreshToken);
@@ -47,10 +33,6 @@ class BloggerService {
47
33
  'GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN (full access).');
48
34
  }
49
35
  }
50
- /**
51
- * Checks that OAuth2 authentication is available.
52
- * Throws an explicit error if the operation requires OAuth2 and we are in API key mode.
53
- */
54
36
  requireOAuth2(operation) {
55
37
  if (!this.isOAuth2) {
56
38
  throw new Error(`Operation "${operation}" requires OAuth2 authentication. ` +
@@ -58,11 +40,6 @@ class BloggerService {
58
40
  'Configure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REFRESH_TOKEN.');
59
41
  }
60
42
  }
61
- /**
62
- * Lists all blogs for the authenticated user.
63
- * Requires OAuth2 (blogs.listByUser with userId: 'self').
64
- * @returns Blog list
65
- */
66
43
  async listBlogs() {
67
44
  this.requireOAuth2('list_blogs');
68
45
  try {
@@ -76,16 +53,9 @@ class BloggerService {
76
53
  throw error;
77
54
  }
78
55
  }
79
- /**
80
- * Retrieves details of a specific blog
81
- * @param blogId ID of the blog to retrieve
82
- * @returns Blog details
83
- */
84
56
  async getBlog(blogId) {
85
57
  try {
86
- const response = await this.blogger.blogs.get({
87
- blogId
88
- });
58
+ const response = await this.blogger.blogs.get({ blogId });
89
59
  return response.data;
90
60
  }
91
61
  catch (error) {
@@ -93,16 +63,9 @@ class BloggerService {
93
63
  throw error;
94
64
  }
95
65
  }
96
- /**
97
- * Retrieves a blog by its URL
98
- * @param url Blog URL
99
- * @returns Blog details
100
- */
101
66
  async getBlogByUrl(url) {
102
67
  try {
103
- const response = await this.blogger.blogs.getByUrl({
104
- url
105
- });
68
+ const response = await this.blogger.blogs.getByUrl({ url });
106
69
  return response.data;
107
70
  }
108
71
  catch (error) {
@@ -110,31 +73,6 @@ class BloggerService {
110
73
  throw error;
111
74
  }
112
75
  }
113
- /**
114
- * Simulates blog creation.
115
- * Note: The Blogger API does not actually allow creating a blog via API.
116
- * This method simulates the functionality and returns an explanatory error message.
117
- *
118
- * @param blogData Blog data to create
119
- * @returns Explanatory error message
120
- */
121
- async createBlog(blogData) {
122
- // Simulate a delay to make the response more realistic
123
- await new Promise(resolve => setTimeout(resolve, 500));
124
- // Return an explanatory error message
125
- return {
126
- error: true,
127
- message: "The Google Blogger API does not allow creating a new blog via API. Please create a blog manually on blogger.com.",
128
- details: "This limitation is documented by Google. Blogs must be created via the Blogger web interface.",
129
- suggestedAction: "Create a blog at https://www.blogger.com, then use its ID with this MCP server."
130
- };
131
- }
132
- /**
133
- * Lists posts from a blog
134
- * @param blogId Blog ID
135
- * @param maxResults Maximum number of results to return
136
- * @returns Post list
137
- */
138
76
  async listPosts(blogId, maxResults) {
139
77
  try {
140
78
  const response = await this.blogger.posts.list({
@@ -148,13 +86,6 @@ class BloggerService {
148
86
  throw error;
149
87
  }
150
88
  }
151
- /**
152
- * Searches posts in a blog using the native posts.search endpoint of the Blogger API
153
- * @param blogId Blog ID
154
- * @param query Search term
155
- * @param maxResults Maximum number of results to return
156
- * @returns List of matching posts
157
- */
158
89
  async searchPosts(blogId, query, maxResults) {
159
90
  try {
160
91
  const response = await this.blogger.posts.search({
@@ -162,8 +93,6 @@ class BloggerService {
162
93
  q: query,
163
94
  fetchBodies: true
164
95
  });
165
- // The search endpoint does not support maxResults directly,
166
- // so we truncate client-side if needed
167
96
  const items = response.data.items || [];
168
97
  const limit = maxResults || config_1.config.blogger.maxResults;
169
98
  return {
@@ -176,18 +105,9 @@ class BloggerService {
176
105
  throw error;
177
106
  }
178
107
  }
179
- /**
180
- * Retrieves a specific post
181
- * @param blogId Blog ID
182
- * @param postId Post ID
183
- * @returns Post details
184
- */
185
108
  async getPost(blogId, postId) {
186
109
  try {
187
- const response = await this.blogger.posts.get({
188
- blogId,
189
- postId
190
- });
110
+ const response = await this.blogger.posts.get({ blogId, postId });
191
111
  return response.data;
192
112
  }
193
113
  catch (error) {
@@ -195,20 +115,15 @@ class BloggerService {
195
115
  throw error;
196
116
  }
197
117
  }
198
- /**
199
- * Creates a new post in a blog.
200
- * Requires OAuth2.
201
- * @param blogId Blog ID
202
- * @param postData Post data to create
203
- * @returns Created post
204
- */
205
118
  async createPost(blogId, postData) {
206
119
  this.requireOAuth2('create_post');
207
120
  try {
208
- const response = await this.blogger.posts.insert({
209
- blogId,
210
- requestBody: postData
211
- });
121
+ const requestBody = {
122
+ title: postData.title ?? undefined,
123
+ content: postData.content ?? undefined,
124
+ labels: postData.labels ?? undefined
125
+ };
126
+ const response = await this.blogger.posts.insert({ blogId, requestBody });
212
127
  return response.data;
213
128
  }
214
129
  catch (error) {
@@ -216,28 +131,15 @@ class BloggerService {
216
131
  throw error;
217
132
  }
218
133
  }
219
- /**
220
- * Updates an existing post.
221
- * Requires OAuth2.
222
- * @param blogId Blog ID
223
- * @param postId Post ID
224
- * @param postData Post data to update
225
- * @returns Updated post
226
- */
227
134
  async updatePost(blogId, postId, postData) {
228
135
  this.requireOAuth2('update_post');
229
136
  try {
230
- // Convert types to avoid compilation errors
231
137
  const requestBody = {
232
- title: postData.title,
233
- content: postData.content,
234
- labels: postData.labels
138
+ title: postData.title ?? undefined,
139
+ content: postData.content ?? undefined,
140
+ labels: postData.labels ?? undefined
235
141
  };
236
- const response = await this.blogger.posts.update({
237
- blogId,
238
- postId,
239
- requestBody
240
- });
142
+ const response = await this.blogger.posts.update({ blogId, postId, requestBody });
241
143
  return response.data;
242
144
  }
243
145
  catch (error) {
@@ -245,68 +147,37 @@ class BloggerService {
245
147
  throw error;
246
148
  }
247
149
  }
248
- /**
249
- * Deletes a post.
250
- * Requires OAuth2.
251
- * @param blogId Blog ID
252
- * @param postId Post ID
253
- * @returns Deletion status
254
- */
255
150
  async deletePost(blogId, postId) {
256
151
  this.requireOAuth2('delete_post');
257
152
  try {
258
- await this.blogger.posts.delete({
259
- blogId,
260
- postId
261
- });
153
+ await this.blogger.posts.delete({ blogId, postId });
262
154
  }
263
155
  catch (error) {
264
156
  console.error(`Error deleting post ${postId}:`, error);
265
157
  throw error;
266
158
  }
267
159
  }
268
- /**
269
- * Lists labels from a blog
270
- * @param blogId Blog ID
271
- * @returns Label list
272
- */
273
160
  async listLabels(blogId) {
274
161
  try {
275
- // The Blogger API does not provide a direct endpoint to list labels
276
- // We fetch all posts and extract unique labels
277
162
  const response = await this.blogger.posts.list({
278
163
  blogId,
279
- maxResults: 50 // Fetch enough posts to extract labels
164
+ maxResults: 50
280
165
  });
281
166
  const posts = response.data.items || [];
282
167
  const labelSet = new Set();
283
- // Extract all unique labels from posts
284
168
  posts.forEach(post => {
285
- const postLabels = post.labels || [];
286
- postLabels.forEach(label => labelSet.add(label));
169
+ (post.labels || []).forEach(label => labelSet.add(label));
287
170
  });
288
- // Convert to expected format
289
171
  const labels = Array.from(labelSet).map(name => ({ name }));
290
- return {
291
- kind: 'blogger#labelList',
292
- items: labels
293
- };
172
+ return { kind: 'blogger#labelList', items: labels };
294
173
  }
295
174
  catch (error) {
296
175
  console.error(`Error fetching labels for blog ${blogId}:`, error);
297
176
  throw error;
298
177
  }
299
178
  }
300
- /**
301
- * Retrieves a specific label
302
- * @param blogId Blog ID
303
- * @param labelName Label name
304
- * @returns Label details
305
- */
306
179
  async getLabel(blogId, labelName) {
307
180
  try {
308
- // The Blogger API does not provide a direct endpoint to retrieve a label
309
- // We check if the label exists by listing all labels
310
181
  const labels = await this.listLabels(blogId);
311
182
  const label = labels.items?.find(l => l.name === labelName);
312
183
  if (!label) {
package/dist/config.d.ts CHANGED
@@ -17,4 +17,7 @@ export declare const config: {
17
17
  logging: {
18
18
  level: string;
19
19
  };
20
+ ui: {
21
+ port: number;
22
+ };
20
23
  };
package/dist/config.js CHANGED
@@ -1,32 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.config = void 0;
4
- // MCP server configuration for Blogger
4
+ function safeInt(value, defaultValue) {
5
+ if (!value)
6
+ return defaultValue;
7
+ const parsed = parseInt(value, 10);
8
+ return Number.isNaN(parsed) ? defaultValue : parsed;
9
+ }
5
10
  exports.config = {
6
- // Server operating mode (stdio or http)
7
11
  mode: process.env.MCP_MODE || 'stdio',
8
- // HTTP mode configuration (if used)
9
12
  http: {
10
13
  host: process.env.MCP_HTTP_HOST || '0.0.0.0',
11
- port: parseInt(process.env.MCP_HTTP_PORT || '3000', 10)
14
+ port: safeInt(process.env.MCP_HTTP_PORT, 3000)
12
15
  },
13
- // Blogger API configuration
14
16
  blogger: {
15
17
  apiKey: process.env.BLOGGER_API_KEY,
16
- // Default maximum number of results for list queries
17
- maxResults: parseInt(process.env.BLOGGER_MAX_RESULTS || '10', 10),
18
- // API request timeout in milliseconds
19
- timeout: parseInt(process.env.BLOGGER_API_TIMEOUT || '30000', 10)
18
+ maxResults: safeInt(process.env.BLOGGER_MAX_RESULTS, 10),
19
+ timeout: safeInt(process.env.BLOGGER_API_TIMEOUT, 30000)
20
20
  },
21
- // OAuth2 configuration for authenticated operations (create, update, delete)
22
- // If these variables are not set, the server runs in read-only mode (API key)
23
21
  oauth2: {
24
22
  clientId: process.env.GOOGLE_CLIENT_ID,
25
23
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
26
24
  refreshToken: process.env.GOOGLE_REFRESH_TOKEN
27
25
  },
28
- // Logging configuration
29
26
  logging: {
30
27
  level: process.env.LOG_LEVEL || 'info'
28
+ },
29
+ ui: {
30
+ port: safeInt(process.env.UI_PORT, 0)
31
31
  }
32
32
  };