mcpeasy 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.claudeignore +0 -3
- data/.mcp.json +10 -1
- data/CHANGELOG.md +50 -0
- data/CLAUDE.md +19 -5
- data/README.md +19 -3
- data/lib/mcpeasy/cli.rb +33 -10
- data/lib/mcpeasy/config.rb +22 -1
- data/lib/mcpeasy/setup.rb +1 -0
- data/lib/mcpeasy/version.rb +1 -1
- data/lib/utilities/gcal/README.md +11 -3
- data/lib/utilities/gcal/cli.rb +110 -108
- data/lib/utilities/gcal/mcp.rb +463 -308
- data/lib/utilities/gcal/service.rb +312 -0
- data/lib/utilities/gdrive/README.md +3 -3
- data/lib/utilities/gdrive/cli.rb +98 -96
- data/lib/utilities/gdrive/mcp.rb +290 -288
- data/lib/utilities/gdrive/service.rb +293 -0
- data/lib/utilities/gmeet/cli.rb +131 -129
- data/lib/utilities/gmeet/mcp.rb +374 -372
- data/lib/utilities/gmeet/service.rb +409 -0
- data/lib/utilities/notion/README.md +287 -0
- data/lib/utilities/notion/cli.rb +245 -0
- data/lib/utilities/notion/mcp.rb +607 -0
- data/lib/utilities/notion/service.rb +327 -0
- data/lib/utilities/slack/README.md +3 -3
- data/lib/utilities/slack/cli.rb +69 -54
- data/lib/utilities/slack/mcp.rb +277 -226
- data/lib/utilities/slack/service.rb +134 -0
- metadata +11 -8
- data/env.template +0 -11
- data/lib/utilities/gcal/gcal_tool.rb +0 -308
- data/lib/utilities/gdrive/gdrive_tool.rb +0 -291
- data/lib/utilities/gmeet/gmeet_tool.rb +0 -407
- data/lib/utilities/slack/slack_tool.rb +0 -119
- data/logs/.keep +0 -0
@@ -0,0 +1,607 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "json"
|
6
|
+
require_relative "service"
|
7
|
+
|
8
|
+
module Notion
|
9
|
+
class MCPServer
|
10
|
+
def initialize
|
11
|
+
# Defer Service initialization until actually needed
|
12
|
+
@notion_tool = nil
|
13
|
+
@tools = {
|
14
|
+
"test_connection" => {
|
15
|
+
name: "test_connection",
|
16
|
+
description: "Test the Notion API connection",
|
17
|
+
inputSchema: {
|
18
|
+
type: "object",
|
19
|
+
properties: {},
|
20
|
+
required: []
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"search_pages" => {
|
24
|
+
name: "search_pages",
|
25
|
+
description: "Search for pages in Notion workspace",
|
26
|
+
inputSchema: {
|
27
|
+
type: "object",
|
28
|
+
properties: {
|
29
|
+
query: {
|
30
|
+
type: "string",
|
31
|
+
description: "Search query to find pages (optional, searches all pages if empty)"
|
32
|
+
},
|
33
|
+
page_size: {
|
34
|
+
type: "number",
|
35
|
+
description: "Maximum number of results to return (default: 10, max: 100)"
|
36
|
+
}
|
37
|
+
},
|
38
|
+
required: []
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"search_databases" => {
|
42
|
+
name: "search_databases",
|
43
|
+
description: "Search for databases in Notion workspace",
|
44
|
+
inputSchema: {
|
45
|
+
type: "object",
|
46
|
+
properties: {
|
47
|
+
query: {
|
48
|
+
type: "string",
|
49
|
+
description: "Search query to find databases (optional, searches all databases if empty)"
|
50
|
+
},
|
51
|
+
page_size: {
|
52
|
+
type: "number",
|
53
|
+
description: "Maximum number of results to return (default: 10, max: 100)"
|
54
|
+
}
|
55
|
+
},
|
56
|
+
required: []
|
57
|
+
}
|
58
|
+
},
|
59
|
+
"get_page" => {
|
60
|
+
name: "get_page",
|
61
|
+
description: "Get details of a specific Notion page",
|
62
|
+
inputSchema: {
|
63
|
+
type: "object",
|
64
|
+
properties: {
|
65
|
+
page_id: {
|
66
|
+
type: "string",
|
67
|
+
description: "The ID of the Notion page to retrieve"
|
68
|
+
}
|
69
|
+
},
|
70
|
+
required: ["page_id"]
|
71
|
+
}
|
72
|
+
},
|
73
|
+
"get_page_content" => {
|
74
|
+
name: "get_page_content",
|
75
|
+
description: "Get the text content of a Notion page",
|
76
|
+
inputSchema: {
|
77
|
+
type: "object",
|
78
|
+
properties: {
|
79
|
+
page_id: {
|
80
|
+
type: "string",
|
81
|
+
description: "The ID of the Notion page to get content from"
|
82
|
+
}
|
83
|
+
},
|
84
|
+
required: ["page_id"]
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"query_database" => {
|
88
|
+
name: "query_database",
|
89
|
+
description: "Query entries in a Notion database",
|
90
|
+
inputSchema: {
|
91
|
+
type: "object",
|
92
|
+
properties: {
|
93
|
+
database_id: {
|
94
|
+
type: "string",
|
95
|
+
description: "The ID of the Notion database to query"
|
96
|
+
},
|
97
|
+
page_size: {
|
98
|
+
type: "number",
|
99
|
+
description: "Maximum number of results to return (default: 100, max: 100)"
|
100
|
+
},
|
101
|
+
start_cursor: {
|
102
|
+
type: "string",
|
103
|
+
description: "Cursor for pagination. Use the next_cursor from previous response to get next page"
|
104
|
+
}
|
105
|
+
},
|
106
|
+
required: ["database_id"]
|
107
|
+
}
|
108
|
+
},
|
109
|
+
"list_users" => {
|
110
|
+
name: "list_users",
|
111
|
+
description: "List all users in the Notion workspace",
|
112
|
+
inputSchema: {
|
113
|
+
type: "object",
|
114
|
+
properties: {
|
115
|
+
page_size: {
|
116
|
+
type: "number",
|
117
|
+
description: "Maximum number of results to return (default: 100, max: 100)"
|
118
|
+
},
|
119
|
+
start_cursor: {
|
120
|
+
type: "string",
|
121
|
+
description: "Cursor for pagination. Use the next_cursor from previous response to get next page"
|
122
|
+
}
|
123
|
+
},
|
124
|
+
required: []
|
125
|
+
}
|
126
|
+
},
|
127
|
+
"get_user" => {
|
128
|
+
name: "get_user",
|
129
|
+
description: "Get details of a specific Notion user",
|
130
|
+
inputSchema: {
|
131
|
+
type: "object",
|
132
|
+
properties: {
|
133
|
+
user_id: {
|
134
|
+
type: "string",
|
135
|
+
description: "The ID of the Notion user to retrieve"
|
136
|
+
}
|
137
|
+
},
|
138
|
+
required: ["user_id"]
|
139
|
+
}
|
140
|
+
},
|
141
|
+
"get_bot_user" => {
|
142
|
+
name: "get_bot_user",
|
143
|
+
description: "Get information about the integration bot user",
|
144
|
+
inputSchema: {
|
145
|
+
type: "object",
|
146
|
+
properties: {},
|
147
|
+
required: []
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def run
|
154
|
+
# Disable stdout buffering for immediate response
|
155
|
+
$stdout.sync = true
|
156
|
+
|
157
|
+
# Log startup to file instead of stdout to avoid protocol interference
|
158
|
+
Mcpeasy::Config.ensure_config_dirs
|
159
|
+
File.write(Mcpeasy::Config.log_file_path("notion", "startup"), "#{Time.now}: Notion MCP Server starting on stdio\n", mode: "a")
|
160
|
+
while (line = $stdin.gets)
|
161
|
+
handle_request(line.strip)
|
162
|
+
end
|
163
|
+
rescue Interrupt
|
164
|
+
# Silent shutdown
|
165
|
+
rescue => e
|
166
|
+
# Log to a file instead of stderr to avoid protocol interference
|
167
|
+
File.write(Mcpeasy::Config.log_file_path("notion", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def handle_request(line)
|
173
|
+
return if line.empty?
|
174
|
+
|
175
|
+
begin
|
176
|
+
request = JSON.parse(line)
|
177
|
+
response = process_request(request)
|
178
|
+
if response
|
179
|
+
puts JSON.generate(response)
|
180
|
+
$stdout.flush
|
181
|
+
end
|
182
|
+
rescue JSON::ParserError => e
|
183
|
+
error_response = {
|
184
|
+
jsonrpc: "2.0",
|
185
|
+
id: nil,
|
186
|
+
error: {
|
187
|
+
code: -32700,
|
188
|
+
message: "Parse error",
|
189
|
+
data: e.message
|
190
|
+
}
|
191
|
+
}
|
192
|
+
puts JSON.generate(error_response)
|
193
|
+
$stdout.flush
|
194
|
+
rescue => e
|
195
|
+
File.write(Mcpeasy::Config.log_file_path("notion", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
196
|
+
error_response = {
|
197
|
+
jsonrpc: "2.0",
|
198
|
+
id: request&.dig("id"),
|
199
|
+
error: {
|
200
|
+
code: -32603,
|
201
|
+
message: "Internal error",
|
202
|
+
data: e.message
|
203
|
+
}
|
204
|
+
}
|
205
|
+
puts JSON.generate(error_response)
|
206
|
+
$stdout.flush
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def process_request(request)
|
211
|
+
id = request["id"]
|
212
|
+
method = request["method"]
|
213
|
+
params = request["params"] || {}
|
214
|
+
|
215
|
+
case method
|
216
|
+
when "notifications/initialized"
|
217
|
+
# Client acknowledgment - no response needed
|
218
|
+
nil
|
219
|
+
when "initialize"
|
220
|
+
initialize_response(id, params)
|
221
|
+
when "tools/list"
|
222
|
+
tools_list_response(id, params)
|
223
|
+
when "tools/call"
|
224
|
+
tools_call_response(id, params)
|
225
|
+
else
|
226
|
+
{
|
227
|
+
jsonrpc: "2.0",
|
228
|
+
id: id,
|
229
|
+
error: {
|
230
|
+
code: -32601,
|
231
|
+
message: "Method not found",
|
232
|
+
data: "Unknown method: #{method}"
|
233
|
+
}
|
234
|
+
}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def initialize_response(id, params)
|
239
|
+
{
|
240
|
+
jsonrpc: "2.0",
|
241
|
+
id: id,
|
242
|
+
result: {
|
243
|
+
protocolVersion: "2024-11-05",
|
244
|
+
capabilities: {
|
245
|
+
tools: {}
|
246
|
+
},
|
247
|
+
serverInfo: {
|
248
|
+
name: "notion-mcp-server",
|
249
|
+
version: "1.0.0"
|
250
|
+
}
|
251
|
+
}
|
252
|
+
}
|
253
|
+
end
|
254
|
+
|
255
|
+
def tools_list_response(id, params)
|
256
|
+
{
|
257
|
+
jsonrpc: "2.0",
|
258
|
+
id: id,
|
259
|
+
result: {
|
260
|
+
tools: @tools.values
|
261
|
+
}
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
def tools_call_response(id, params)
|
266
|
+
tool_name = params["name"]
|
267
|
+
arguments = params["arguments"] || {}
|
268
|
+
|
269
|
+
unless @tools.key?(tool_name)
|
270
|
+
return {
|
271
|
+
jsonrpc: "2.0",
|
272
|
+
id: id,
|
273
|
+
error: {
|
274
|
+
code: -32602,
|
275
|
+
message: "Unknown tool",
|
276
|
+
data: "Tool '#{tool_name}' not found"
|
277
|
+
}
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
begin
|
282
|
+
result = call_tool(tool_name, arguments)
|
283
|
+
{
|
284
|
+
jsonrpc: "2.0",
|
285
|
+
id: id,
|
286
|
+
result: {
|
287
|
+
content: [
|
288
|
+
{
|
289
|
+
type: "text",
|
290
|
+
text: result
|
291
|
+
}
|
292
|
+
],
|
293
|
+
isError: false
|
294
|
+
}
|
295
|
+
}
|
296
|
+
rescue => e
|
297
|
+
File.write(Mcpeasy::Config.log_file_path("notion", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
298
|
+
{
|
299
|
+
jsonrpc: "2.0",
|
300
|
+
id: id,
|
301
|
+
result: {
|
302
|
+
content: [
|
303
|
+
{
|
304
|
+
type: "text",
|
305
|
+
text: "❌ Error: #{e.message}"
|
306
|
+
}
|
307
|
+
],
|
308
|
+
isError: true
|
309
|
+
}
|
310
|
+
}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def call_tool(tool_name, arguments)
|
315
|
+
# Initialize Service only when needed
|
316
|
+
@notion_tool ||= Service.new
|
317
|
+
|
318
|
+
case tool_name
|
319
|
+
when "test_connection"
|
320
|
+
test_connection
|
321
|
+
when "search_pages"
|
322
|
+
search_pages(arguments)
|
323
|
+
when "search_databases"
|
324
|
+
search_databases(arguments)
|
325
|
+
when "get_page"
|
326
|
+
get_page(arguments)
|
327
|
+
when "get_page_content"
|
328
|
+
get_page_content(arguments)
|
329
|
+
when "query_database"
|
330
|
+
query_database(arguments)
|
331
|
+
when "list_users"
|
332
|
+
list_users(arguments)
|
333
|
+
when "get_user"
|
334
|
+
get_user(arguments)
|
335
|
+
when "get_bot_user"
|
336
|
+
get_bot_user
|
337
|
+
else
|
338
|
+
raise "Unknown tool: #{tool_name}"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_connection
|
343
|
+
response = @notion_tool.test_connection
|
344
|
+
if response[:ok]
|
345
|
+
"✅ Successfully connected to Notion. User: #{response[:user]}, Type: #{response[:type]}"
|
346
|
+
else
|
347
|
+
raise "Authentication failed: #{response[:error]}"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def search_pages(arguments)
|
352
|
+
query = arguments["query"]&.to_s || ""
|
353
|
+
page_size = [arguments["page_size"]&.to_i || 10, 100].min
|
354
|
+
|
355
|
+
pages = @notion_tool.search_pages(query: query, page_size: page_size)
|
356
|
+
|
357
|
+
query_text = query.empty? ? "" : " for query '#{query}'"
|
358
|
+
pages_list = pages.map.with_index do |page, i|
|
359
|
+
<<~PAGE
|
360
|
+
#{i + 1}. **#{page[:title]}**
|
361
|
+
- ID: `#{page[:id]}`
|
362
|
+
- URL: #{page[:url]}
|
363
|
+
- Last edited: #{page[:last_edited_time]}
|
364
|
+
PAGE
|
365
|
+
end.join("\n")
|
366
|
+
|
367
|
+
<<~OUTPUT
|
368
|
+
📄 Found #{pages.count} pages#{query_text}:
|
369
|
+
|
370
|
+
#{pages_list}
|
371
|
+
OUTPUT
|
372
|
+
end
|
373
|
+
|
374
|
+
def search_databases(arguments)
|
375
|
+
query = arguments["query"]&.to_s || ""
|
376
|
+
page_size = [arguments["page_size"]&.to_i || 10, 100].min
|
377
|
+
|
378
|
+
databases = @notion_tool.search_databases(query: query, page_size: page_size)
|
379
|
+
|
380
|
+
query_text = query.empty? ? "" : " for query '#{query}'"
|
381
|
+
databases_list = databases.map.with_index do |database, i|
|
382
|
+
<<~DATABASE
|
383
|
+
#{i + 1}. **#{database[:title]}**
|
384
|
+
- ID: `#{database[:id]}`
|
385
|
+
- URL: #{database[:url]}
|
386
|
+
- Last edited: #{database[:last_edited_time]}
|
387
|
+
DATABASE
|
388
|
+
end.join("\n")
|
389
|
+
|
390
|
+
<<~OUTPUT
|
391
|
+
🗃️ Found #{databases.count} databases#{query_text}:
|
392
|
+
|
393
|
+
#{databases_list}
|
394
|
+
OUTPUT
|
395
|
+
end
|
396
|
+
|
397
|
+
def get_page(arguments)
|
398
|
+
unless arguments["page_id"]
|
399
|
+
raise "Missing required argument: page_id"
|
400
|
+
end
|
401
|
+
|
402
|
+
page_id = arguments["page_id"].to_s
|
403
|
+
page = @notion_tool.get_page(page_id)
|
404
|
+
|
405
|
+
properties_section = if page[:properties]&.any?
|
406
|
+
properties_lines = page[:properties].map do |name, prop|
|
407
|
+
formatted_value = format_property_for_mcp(prop)
|
408
|
+
"- **#{name}:** #{formatted_value}"
|
409
|
+
end
|
410
|
+
"\n**Properties:**\n#{properties_lines.join("\n")}\n"
|
411
|
+
else
|
412
|
+
""
|
413
|
+
end
|
414
|
+
|
415
|
+
<<~OUTPUT
|
416
|
+
📄 **Page Details**
|
417
|
+
|
418
|
+
**Title:** #{page[:title]}
|
419
|
+
**ID:** `#{page[:id]}`
|
420
|
+
**URL:** #{page[:url]}
|
421
|
+
**Created:** #{page[:created_time]}
|
422
|
+
**Last edited:** #{page[:last_edited_time]}#{properties_section}
|
423
|
+
OUTPUT
|
424
|
+
end
|
425
|
+
|
426
|
+
def get_page_content(arguments)
|
427
|
+
unless arguments["page_id"]
|
428
|
+
raise "Missing required argument: page_id"
|
429
|
+
end
|
430
|
+
|
431
|
+
page_id = arguments["page_id"].to_s
|
432
|
+
content = @notion_tool.get_page_content(page_id)
|
433
|
+
|
434
|
+
if content && !content.empty?
|
435
|
+
"📝 **Page Content:**\n\n#{content}"
|
436
|
+
else
|
437
|
+
"📝 No content found for this page"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def query_database(arguments)
|
442
|
+
unless arguments["database_id"]
|
443
|
+
raise "Missing required argument: database_id"
|
444
|
+
end
|
445
|
+
|
446
|
+
database_id = arguments["database_id"].to_s
|
447
|
+
page_size = [arguments["page_size"]&.to_i || 100, 100].min
|
448
|
+
start_cursor = arguments["start_cursor"]
|
449
|
+
|
450
|
+
# Keep track of current page for display
|
451
|
+
@query_database_page ||= {}
|
452
|
+
@query_database_page[database_id] ||= 0
|
453
|
+
@query_database_page[database_id] = start_cursor ? @query_database_page[database_id] + 1 : 1
|
454
|
+
|
455
|
+
result = @notion_tool.query_database(database_id, page_size: page_size, start_cursor: start_cursor)
|
456
|
+
entries = result[:entries]
|
457
|
+
|
458
|
+
# Calculate record range
|
459
|
+
page_num = @query_database_page[database_id]
|
460
|
+
start_index = (page_num - 1) * page_size
|
461
|
+
end_index = start_index + entries.count - 1
|
462
|
+
|
463
|
+
entries_list = entries.map.with_index do |entry, i|
|
464
|
+
<<~ENTRY
|
465
|
+
#{start_index + i + 1}. **#{entry[:title]}**
|
466
|
+
- ID: `#{entry[:id]}`
|
467
|
+
- URL: #{entry[:url]}
|
468
|
+
- Last edited: #{entry[:last_edited_time]}
|
469
|
+
ENTRY
|
470
|
+
end.join("\n")
|
471
|
+
|
472
|
+
pagination_info = if result[:has_more]
|
473
|
+
<<~INFO
|
474
|
+
|
475
|
+
📄 **Page #{page_num}** | Showing records #{start_index + 1}-#{end_index + 1}
|
476
|
+
_More entries available. Use `start_cursor: "#{result[:next_cursor]}"` to get the next page._
|
477
|
+
INFO
|
478
|
+
else
|
479
|
+
# Try to estimate total if we're on last page
|
480
|
+
estimated_total = start_index + entries.count
|
481
|
+
<<~INFO
|
482
|
+
|
483
|
+
📄 **Page #{page_num}** | Showing records #{start_index + 1}-#{end_index + 1} of #{estimated_total} total
|
484
|
+
INFO
|
485
|
+
end
|
486
|
+
|
487
|
+
<<~OUTPUT
|
488
|
+
🗃️ Found #{entries.count} entries in database:
|
489
|
+
|
490
|
+
#{entries_list}#{pagination_info}
|
491
|
+
OUTPUT
|
492
|
+
end
|
493
|
+
|
494
|
+
def list_users(arguments)
|
495
|
+
page_size = [arguments["page_size"]&.to_i || 100, 100].min
|
496
|
+
start_cursor = arguments["start_cursor"]
|
497
|
+
|
498
|
+
# Keep track of current page for display
|
499
|
+
@list_users_page ||= 0
|
500
|
+
@list_users_page = start_cursor ? @list_users_page + 1 : 1
|
501
|
+
|
502
|
+
result = @notion_tool.list_users(page_size: page_size, start_cursor: start_cursor)
|
503
|
+
users = result[:users]
|
504
|
+
|
505
|
+
# Calculate record range
|
506
|
+
start_index = (@list_users_page - 1) * page_size
|
507
|
+
end_index = start_index + users.count - 1
|
508
|
+
|
509
|
+
users_list = users.map.with_index do |user, i|
|
510
|
+
email_line = user[:email] ? "\n - Email: #{user[:email]}" : ""
|
511
|
+
avatar_line = user[:avatar_url] ? "\n - Avatar: #{user[:avatar_url]}" : ""
|
512
|
+
|
513
|
+
<<~USER
|
514
|
+
#{start_index + i + 1}. **#{user[:name] || "Unnamed"}** (#{user[:type]})
|
515
|
+
- ID: `#{user[:id]}`#{email_line}#{avatar_line}
|
516
|
+
USER
|
517
|
+
end.join("\n")
|
518
|
+
|
519
|
+
pagination_info = if result[:has_more]
|
520
|
+
<<~INFO
|
521
|
+
|
522
|
+
📄 **Page #{@list_users_page}** | Showing records #{start_index + 1}-#{end_index + 1}
|
523
|
+
_More users available. Use `start_cursor: "#{result[:next_cursor]}"` to get the next page._
|
524
|
+
INFO
|
525
|
+
else
|
526
|
+
# Try to estimate total if we're on last page
|
527
|
+
estimated_total = start_index + users.count
|
528
|
+
<<~INFO
|
529
|
+
|
530
|
+
📄 **Page #{@list_users_page}** | Showing records #{start_index + 1}-#{end_index + 1} of #{estimated_total} total
|
531
|
+
INFO
|
532
|
+
end
|
533
|
+
|
534
|
+
<<~OUTPUT
|
535
|
+
👥 Found #{users.count} users in workspace:
|
536
|
+
|
537
|
+
#{users_list}#{pagination_info}
|
538
|
+
OUTPUT
|
539
|
+
end
|
540
|
+
|
541
|
+
def get_user(arguments)
|
542
|
+
unless arguments["user_id"]
|
543
|
+
raise "Missing required argument: user_id"
|
544
|
+
end
|
545
|
+
|
546
|
+
user_id = arguments["user_id"].to_s
|
547
|
+
user = @notion_tool.get_user(user_id)
|
548
|
+
|
549
|
+
email_line = user[:email] ? "\n**Email:** #{user[:email]}" : ""
|
550
|
+
avatar_line = user[:avatar_url] ? "\n**Avatar:** #{user[:avatar_url]}" : ""
|
551
|
+
|
552
|
+
<<~OUTPUT
|
553
|
+
👤 **User Details**
|
554
|
+
|
555
|
+
**Name:** #{user[:name] || "Unnamed"}
|
556
|
+
**Type:** #{user[:type]}
|
557
|
+
**ID:** `#{user[:id]}`#{email_line}#{avatar_line}
|
558
|
+
OUTPUT
|
559
|
+
end
|
560
|
+
|
561
|
+
def get_bot_user
|
562
|
+
bot = @notion_tool.get_bot_user
|
563
|
+
|
564
|
+
workspace_line = bot[:bot][:workspace_name] ? "\n**Workspace:** #{bot[:bot][:workspace_name]}" : ""
|
565
|
+
owner_line = bot[:bot][:owner] ? "\n**Owner:** #{bot[:bot][:owner]}" : ""
|
566
|
+
|
567
|
+
<<~OUTPUT
|
568
|
+
🤖 **Bot User Details**
|
569
|
+
|
570
|
+
**Name:** #{bot[:name] || "Unnamed"}
|
571
|
+
**Type:** #{bot[:type]}
|
572
|
+
**ID:** `#{bot[:id]}`#{workspace_line}#{owner_line}
|
573
|
+
OUTPUT
|
574
|
+
end
|
575
|
+
|
576
|
+
def format_property_for_mcp(prop)
|
577
|
+
case prop["type"]
|
578
|
+
when "title"
|
579
|
+
prop["title"]&.map { |t| t["plain_text"] }&.join || ""
|
580
|
+
when "rich_text"
|
581
|
+
prop["rich_text"]&.map { |t| t["plain_text"] }&.join || ""
|
582
|
+
when "number"
|
583
|
+
prop["number"]&.to_s || ""
|
584
|
+
when "select"
|
585
|
+
prop["select"]&.dig("name") || ""
|
586
|
+
when "multi_select"
|
587
|
+
prop["multi_select"]&.map { |s| s["name"] }&.join(", ") || ""
|
588
|
+
when "date"
|
589
|
+
prop["date"]&.dig("start") || ""
|
590
|
+
when "checkbox"
|
591
|
+
prop["checkbox"] ? "☑" : "☐"
|
592
|
+
when "url"
|
593
|
+
prop["url"] || ""
|
594
|
+
when "email"
|
595
|
+
prop["email"] || ""
|
596
|
+
when "phone_number"
|
597
|
+
prop["phone_number"] || ""
|
598
|
+
else
|
599
|
+
"[#{prop["type"]}]"
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
if __FILE__ == $0
|
606
|
+
Notion::MCPServer.new.run
|
607
|
+
end
|