lex-microsoft_teams 0.6.45 → 0.6.47

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +2 -0
  4. data/CLAUDE.md +29 -266
  5. data/lex-microsoft_teams.gemspec +1 -0
  6. data/lib/legion/extensions/microsoft_teams/absorbers/channel.rb +29 -17
  7. data/lib/legion/extensions/microsoft_teams/absorbers/chat.rb +20 -14
  8. data/lib/legion/extensions/microsoft_teams/absorbers/meeting.rb +21 -14
  9. data/lib/legion/extensions/microsoft_teams/actors/absorb_channel.rb +7 -4
  10. data/lib/legion/extensions/microsoft_teams/actors/absorb_chat.rb +7 -4
  11. data/lib/legion/extensions/microsoft_teams/actors/absorb_meeting.rb +7 -4
  12. data/lib/legion/extensions/microsoft_teams/actors/api_ingest.rb +13 -15
  13. data/lib/legion/extensions/microsoft_teams/actors/cache_bulk_ingest.rb +3 -3
  14. data/lib/legion/extensions/microsoft_teams/actors/cache_sync.rb +2 -1
  15. data/lib/legion/extensions/microsoft_teams/actors/channel_poller.rb +25 -16
  16. data/lib/legion/extensions/microsoft_teams/actors/direct_chat_poller.rb +16 -10
  17. data/lib/legion/extensions/microsoft_teams/actors/incremental_sync.rb +8 -8
  18. data/lib/legion/extensions/microsoft_teams/actors/meeting_ingest.rb +30 -22
  19. data/lib/legion/extensions/microsoft_teams/actors/observed_chat_poller.rb +14 -8
  20. data/lib/legion/extensions/microsoft_teams/actors/presence_poller.rb +14 -8
  21. data/lib/legion/extensions/microsoft_teams/actors/profile_ingest.rb +13 -16
  22. data/lib/legion/extensions/microsoft_teams/helpers/client.rb +10 -4
  23. data/lib/legion/extensions/microsoft_teams/helpers/high_water_mark.rb +3 -2
  24. data/lib/legion/extensions/microsoft_teams/helpers/prompt_resolver.rb +4 -1
  25. data/lib/legion/extensions/microsoft_teams/helpers/session_manager.rb +8 -2
  26. data/lib/legion/extensions/microsoft_teams/helpers/subscription_registry.rb +5 -3
  27. data/lib/legion/extensions/microsoft_teams/helpers/trace_retriever.rb +6 -1
  28. data/lib/legion/extensions/microsoft_teams/local_cache/extractor.rb +1 -1
  29. data/lib/legion/extensions/microsoft_teams/local_cache/sstable_reader.rb +2 -1
  30. data/lib/legion/extensions/microsoft_teams/runners/activities.rb +43 -0
  31. data/lib/legion/extensions/microsoft_teams/runners/ai_insights.rb +62 -0
  32. data/lib/legion/extensions/microsoft_teams/runners/api_ingest.rb +20 -14
  33. data/lib/legion/extensions/microsoft_teams/runners/app_installations.rb +86 -0
  34. data/lib/legion/extensions/microsoft_teams/runners/auth.rb +4 -107
  35. data/lib/legion/extensions/microsoft_teams/runners/bot.rb +20 -12
  36. data/lib/legion/extensions/microsoft_teams/runners/cache_ingest.rb +9 -5
  37. data/lib/legion/extensions/microsoft_teams/runners/call_events.rb +72 -0
  38. data/lib/legion/extensions/microsoft_teams/runners/channel_messages.rb +85 -0
  39. data/lib/legion/extensions/microsoft_teams/runners/channels.rb +69 -0
  40. data/lib/legion/extensions/microsoft_teams/runners/chats.rb +57 -0
  41. data/lib/legion/extensions/microsoft_teams/runners/files.rb +77 -0
  42. data/lib/legion/extensions/microsoft_teams/runners/local_cache.rb +4 -0
  43. data/lib/legion/extensions/microsoft_teams/runners/loop.rb +6 -0
  44. data/lib/legion/extensions/microsoft_teams/runners/meeting_artifacts.rb +54 -0
  45. data/lib/legion/extensions/microsoft_teams/runners/meetings.rb +92 -0
  46. data/lib/legion/extensions/microsoft_teams/runners/messages.rb +62 -0
  47. data/lib/legion/extensions/microsoft_teams/runners/ownership.rb +11 -0
  48. data/lib/legion/extensions/microsoft_teams/runners/people.rb +25 -0
  49. data/lib/legion/extensions/microsoft_teams/runners/presence.rb +14 -0
  50. data/lib/legion/extensions/microsoft_teams/runners/profile_ingest.rb +18 -4
  51. data/lib/legion/extensions/microsoft_teams/runners/subscriptions.rb +86 -0
  52. data/lib/legion/extensions/microsoft_teams/runners/teams.rb +30 -0
  53. data/lib/legion/extensions/microsoft_teams/runners/transcripts.rb +35 -0
  54. data/lib/legion/extensions/microsoft_teams/version.rb +1 -1
  55. data/lib/legion/extensions/microsoft_teams.rb +10 -3
  56. metadata +20 -8
  57. data/lib/legion/extensions/microsoft_teams/actors/auth_validator.rb +0 -123
  58. data/lib/legion/extensions/microsoft_teams/actors/token_refresher.rb +0 -122
  59. data/lib/legion/extensions/microsoft_teams/cli/auth.rb +0 -94
  60. data/lib/legion/extensions/microsoft_teams/helpers/browser_auth.rb +0 -270
  61. data/lib/legion/extensions/microsoft_teams/helpers/callback_server.rb +0 -90
  62. data/lib/legion/extensions/microsoft_teams/helpers/token_cache.rb +0 -412
  63. data/lib/legion/extensions/microsoft_teams/hooks/auth.rb +0 -17
@@ -9,17 +9,56 @@ module Legion
9
9
  module ChannelMessages
10
10
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
11
 
12
+ def self.trigger_words
13
+ %w[post posts feed thread reply]
14
+ end
15
+
16
+ definition :list_channel_messages,
17
+ desc: 'List messages posted in a Teams channel',
18
+ mcp_prefix: 'teams.list_channel_messages',
19
+ mcp_category: 'teams_channel_messages',
20
+ mcp_tier: :standard,
21
+ idempotent: true,
22
+ inputs: { properties: { team_id: { type: 'string' },
23
+ channel_id: { type: 'string' } },
24
+ required: %w[team_id channel_id] },
25
+ trigger_words: %w[channel history posts feed]
26
+
12
27
  def list_channel_messages(team_id:, channel_id:, top: 50, **)
13
28
  params = { '$top' => top }
14
29
  response = graph_connection(**).get("teams/#{team_id}/channels/#{channel_id}/messages", params)
15
30
  { result: response.body }
16
31
  end
17
32
 
33
+ definition :get_channel_message,
34
+ desc: 'Get a specific message from a Teams channel',
35
+ mcp_prefix: 'teams.get_channel_message',
36
+ mcp_category: 'teams_channel_messages',
37
+ mcp_tier: :standard,
38
+ idempotent: true,
39
+ inputs: { properties: { team_id: { type: 'string' },
40
+ channel_id: { type: 'string' },
41
+ message_id: { type: 'string' } },
42
+ required: %w[team_id channel_id message_id] },
43
+ trigger_words: %w[message fetch]
44
+
18
45
  def get_channel_message(team_id:, channel_id:, message_id:, **)
19
46
  response = graph_connection(**).get("teams/#{team_id}/channels/#{channel_id}/messages/#{message_id}")
20
47
  { result: response.body }
21
48
  end
22
49
 
50
+ definition :send_channel_message,
51
+ desc: 'Post a message to a Teams channel',
52
+ mcp_prefix: 'teams.send_channel_message',
53
+ mcp_category: 'teams_channel_messages',
54
+ mcp_tier: :elevated,
55
+ idempotent: false,
56
+ inputs: { properties: { team_id: { type: 'string' },
57
+ channel_id: { type: 'string' },
58
+ content: { type: 'string' } },
59
+ required: %w[team_id channel_id content] },
60
+ trigger_words: %w[post send write]
61
+
23
62
  def send_channel_message(team_id:, channel_id:, content:, content_type: 'text', attachments: [], **)
24
63
  payload = { body: { contentType: content_type, content: content } }
25
64
  payload[:attachments] = attachments unless attachments.empty?
@@ -27,6 +66,19 @@ module Legion
27
66
  { result: response.body }
28
67
  end
29
68
 
69
+ definition :reply_to_channel_message,
70
+ desc: 'Reply to a thread in a Teams channel',
71
+ mcp_prefix: 'teams.reply_to_channel_message',
72
+ mcp_category: 'teams_channel_messages',
73
+ mcp_tier: :elevated,
74
+ idempotent: false,
75
+ inputs: { properties: { team_id: { type: 'string' },
76
+ channel_id: { type: 'string' },
77
+ message_id: { type: 'string' },
78
+ content: { type: 'string' } },
79
+ required: %w[team_id channel_id message_id content] },
80
+ trigger_words: %w[reply respond]
81
+
30
82
  def reply_to_channel_message(team_id:, channel_id:, message_id:, content:, content_type: 'text', **)
31
83
  payload = { body: { contentType: content_type, content: content } }
32
84
  response = graph_connection(**).post(
@@ -35,6 +87,18 @@ module Legion
35
87
  { result: response.body }
36
88
  end
37
89
 
90
+ definition :list_channel_message_replies,
91
+ desc: 'List replies in a Teams channel message thread',
92
+ mcp_prefix: 'teams.list_channel_message_replies',
93
+ mcp_category: 'teams_channel_messages',
94
+ mcp_tier: :standard,
95
+ idempotent: true,
96
+ inputs: { properties: { team_id: { type: 'string' },
97
+ channel_id: { type: 'string' },
98
+ message_id: { type: 'string' } },
99
+ required: %w[team_id channel_id message_id] },
100
+ trigger_words: %w[replies thread]
101
+
38
102
  def list_channel_message_replies(team_id:, channel_id:, message_id:, top: 50, **)
39
103
  params = { '$top' => top }
40
104
  response = graph_connection(**).get(
@@ -43,6 +107,27 @@ module Legion
43
107
  { result: response.body }
44
108
  end
45
109
 
110
+ definition :edit_channel_message,
111
+ desc: 'Edit an existing message in a Teams channel',
112
+ mcp_prefix: 'teams.edit_channel_message',
113
+ mcp_category: 'teams_channel_messages',
114
+ mcp_tier: :elevated,
115
+ idempotent: false,
116
+ inputs: { properties: { team_id: { type: 'string' },
117
+ channel_id: { type: 'string' },
118
+ message_id: { type: 'string' },
119
+ content: { type: 'string' } },
120
+ required: %w[team_id channel_id message_id content] },
121
+ trigger_words: %w[edit update]
122
+
123
+ def edit_channel_message(team_id:, channel_id:, message_id:, content:, content_type: 'text', **)
124
+ payload = { body: { contentType: content_type, content: content } }
125
+ response = graph_connection(**).patch(
126
+ "teams/#{team_id}/channels/#{channel_id}/messages/#{message_id}", payload
127
+ )
128
+ { result: response.body }
129
+ end
130
+
46
131
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
47
132
  Legion::Extensions::Helpers.const_defined?(:Lex, false)
48
133
  end
@@ -7,18 +7,54 @@ module Legion
7
7
  module MicrosoftTeams
8
8
  module Runners
9
9
  module Channels
10
+ extend Legion::Extensions::Definitions
10
11
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
12
 
13
+ def self.trigger_words
14
+ %w[channel channels post posts feed]
15
+ end
16
+
17
+ definition :list_channels,
18
+ desc: 'List channels in a Team',
19
+ mcp_prefix: 'teams.list_channels',
20
+ mcp_category: 'teams_channels',
21
+ mcp_tier: :low,
22
+ idempotent: true,
23
+ inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
24
+ trigger_words: %w[channels list]
25
+
12
26
  def list_channels(team_id:, **)
13
27
  response = graph_connection(**).get("teams/#{team_id}/channels")
14
28
  { result: response.body }
15
29
  end
16
30
 
31
+ definition :get_channel,
32
+ desc: 'Get a specific channel in a Team',
33
+ mcp_prefix: 'teams.get_channel',
34
+ mcp_category: 'teams_channels',
35
+ mcp_tier: :low,
36
+ idempotent: true,
37
+ inputs: { properties: { team_id: { type: 'string' },
38
+ channel_id: { type: 'string' } },
39
+ required: %w[team_id channel_id] },
40
+ trigger_words: %w[channel details]
41
+
17
42
  def get_channel(team_id:, channel_id:, **)
18
43
  response = graph_connection(**).get("teams/#{team_id}/channels/#{channel_id}")
19
44
  { result: response.body }
20
45
  end
21
46
 
47
+ definition :create_channel,
48
+ desc: 'Create a new channel in a Team',
49
+ mcp_prefix: 'teams.create_channel',
50
+ mcp_category: 'teams_channels',
51
+ mcp_tier: :elevated,
52
+ idempotent: false,
53
+ inputs: { properties: { team_id: { type: 'string' },
54
+ display_name: { type: 'string' } },
55
+ required: %w[team_id display_name] },
56
+ trigger_words: %w[create channel]
57
+
22
58
  def create_channel(team_id:, display_name:, description: nil, membership_type: 'standard', **)
23
59
  payload = { displayName: display_name, membershipType: membership_type }
24
60
  payload[:description] = description if description
@@ -26,6 +62,17 @@ module Legion
26
62
  { result: response.body }
27
63
  end
28
64
 
65
+ definition :update_channel,
66
+ desc: 'Update a channel display name or description',
67
+ mcp_prefix: 'teams.update_channel',
68
+ mcp_category: 'teams_channels',
69
+ mcp_tier: :elevated,
70
+ idempotent: false,
71
+ inputs: { properties: { team_id: { type: 'string' },
72
+ channel_id: { type: 'string' } },
73
+ required: %w[team_id channel_id] },
74
+ trigger_words: %w[update rename]
75
+
29
76
  def update_channel(team_id:, channel_id:, display_name: nil, description: nil, **)
30
77
  payload = {}
31
78
  payload[:displayName] = display_name if display_name
@@ -34,11 +81,33 @@ module Legion
34
81
  { result: response.body }
35
82
  end
36
83
 
84
+ definition :delete_channel,
85
+ desc: 'Delete a channel from a Team',
86
+ mcp_prefix: 'teams.delete_channel',
87
+ mcp_category: 'teams_channels',
88
+ mcp_tier: :high,
89
+ idempotent: false,
90
+ inputs: { properties: { team_id: { type: 'string' },
91
+ channel_id: { type: 'string' } },
92
+ required: %w[team_id channel_id] },
93
+ trigger_words: %w[delete remove]
94
+
37
95
  def delete_channel(team_id:, channel_id:, **)
38
96
  response = graph_connection(**).delete("teams/#{team_id}/channels/#{channel_id}")
39
97
  { result: response.body }
40
98
  end
41
99
 
100
+ definition :list_channel_members,
101
+ desc: 'List members of a channel',
102
+ mcp_prefix: 'teams.list_channel_members',
103
+ mcp_category: 'teams_channels',
104
+ mcp_tier: :standard,
105
+ idempotent: true,
106
+ inputs: { properties: { team_id: { type: 'string' },
107
+ channel_id: { type: 'string' } },
108
+ required: %w[team_id channel_id] },
109
+ trigger_words: %w[members roster]
110
+
42
111
  def list_channel_members(team_id:, channel_id:, **)
43
112
  response = graph_connection(**).get("teams/#{team_id}/channels/#{channel_id}/members")
44
113
  { result: response.body }
@@ -7,19 +7,56 @@ module Legion
7
7
  module MicrosoftTeams
8
8
  module Runners
9
9
  module Chats
10
+ extend Legion::Extensions::Definitions
10
11
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
12
 
13
+ def self.trigger_words
14
+ %w[chat chats conversation conversations dm direct]
15
+ end
16
+
17
+ definition :list_chats,
18
+ desc: 'List Teams chats for the current user',
19
+ mcp_prefix: 'teams.list_chats',
20
+ mcp_category: 'teams_chat',
21
+ mcp_tier: :standard,
22
+ idempotent: true,
23
+ trigger_words: %w[chats conversations]
24
+
12
25
  def list_chats(user_id: 'me', top: 50, **)
13
26
  params = { '$top' => top }
14
27
  response = graph_connection(**).get("#{user_path(user_id)}/chats", params)
15
28
  { result: response.body }
16
29
  end
17
30
 
31
+ definition :get_chat,
32
+ desc: 'Get metadata for a specific Teams chat by ID',
33
+ mcp_prefix: 'teams.get_chat',
34
+ mcp_category: 'teams_chat',
35
+ mcp_tier: :standard,
36
+ idempotent: true,
37
+ inputs: { properties: { chat_id: { type: 'string',
38
+ description: 'The Teams chat ID (19:...@thread.v2)' } },
39
+ required: ['chat_id'] },
40
+ trigger_words: %w[chat details]
41
+
18
42
  def get_chat(chat_id:, **)
19
43
  response = graph_connection(**).get("chats/#{chat_id}")
20
44
  { result: response.body }
21
45
  end
22
46
 
47
+ definition :create_chat,
48
+ desc: 'Create a new 1:1 or group Teams chat',
49
+ mcp_prefix: 'teams.create_chat',
50
+ mcp_category: 'teams_chat',
51
+ mcp_tier: :elevated,
52
+ idempotent: false,
53
+ inputs: { properties: { members: { type: 'array',
54
+ description: 'Array of user IDs or UPNs' },
55
+ chat_type: { type: 'string',
56
+ description: 'oneOnOne or group' } },
57
+ required: ['members'] },
58
+ trigger_words: %w[create conversation start]
59
+
23
60
  def create_chat(members:, chat_type: 'oneOnOne', topic: nil, **)
24
61
  payload = { chatType: chat_type, members: members }
25
62
  payload[:topic] = topic if topic
@@ -27,11 +64,31 @@ module Legion
27
64
  { result: response.body }
28
65
  end
29
66
 
67
+ definition :list_chat_members,
68
+ desc: 'List members of a Teams chat',
69
+ mcp_prefix: 'teams.list_chat_members',
70
+ mcp_category: 'teams_chat',
71
+ mcp_tier: :standard,
72
+ idempotent: true,
73
+ inputs: { properties: { chat_id: { type: 'string' } }, required: ['chat_id'] },
74
+ trigger_words: %w[members participants]
75
+
30
76
  def list_chat_members(chat_id:, **)
31
77
  response = graph_connection(**).get("chats/#{chat_id}/members")
32
78
  { result: response.body }
33
79
  end
34
80
 
81
+ definition :add_chat_member,
82
+ desc: 'Add a member to a Teams chat',
83
+ mcp_prefix: 'teams.add_chat_member',
84
+ mcp_category: 'teams_chat',
85
+ mcp_tier: :elevated,
86
+ idempotent: false,
87
+ inputs: { properties: { chat_id: { type: 'string' },
88
+ user_id: { type: 'string' } },
89
+ required: %w[chat_id user_id] },
90
+ trigger_words: %w[invite add member]
91
+
35
92
  def add_chat_member(chat_id:, user_id:, roles: ['owner'], **)
36
93
  payload = {
37
94
  '@odata.type' => '#microsoft.graph.aadUserConversationMember',
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/microsoft_teams/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module MicrosoftTeams
8
+ module Runners
9
+ module Files
10
+ include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
+
12
+ def self.trigger_words
13
+ %w[file files drive onedrive sharepoint document documents]
14
+ end
15
+
16
+ definition :list_drive_items,
17
+ desc: 'List files in the root of a user\'s OneDrive',
18
+ mcp_prefix: 'teams.list_drive_items',
19
+ mcp_category: 'teams_files',
20
+ mcp_tier: :standard,
21
+ idempotent: true,
22
+ trigger_words: %w[files drive onedrive]
23
+
24
+ def list_drive_items(user_id: 'me', **)
25
+ response = graph_connection(**).get("#{user_path(user_id)}/drive/root/children")
26
+ { result: response.body }
27
+ end
28
+
29
+ definition :get_drive_item,
30
+ desc: 'Get metadata for a specific OneDrive file or folder',
31
+ mcp_prefix: 'teams.get_drive_item',
32
+ mcp_category: 'teams_files',
33
+ mcp_tier: :standard,
34
+ idempotent: true,
35
+ inputs: { properties: { item_id: { type: 'string' } }, required: ['item_id'] },
36
+ trigger_words: %w[file item]
37
+
38
+ def get_drive_item(item_id:, user_id: 'me', **)
39
+ response = graph_connection(**).get("#{user_path(user_id)}/drive/items/#{item_id}")
40
+ { result: response.body }
41
+ end
42
+
43
+ definition :get_drive_item_content,
44
+ desc: 'Download the content of a OneDrive file',
45
+ mcp_prefix: 'teams.get_drive_item_content',
46
+ mcp_category: 'teams_files',
47
+ mcp_tier: :standard,
48
+ idempotent: true,
49
+ inputs: { properties: { item_id: { type: 'string' } }, required: ['item_id'] },
50
+ trigger_words: %w[download content read]
51
+
52
+ def get_drive_item_content(item_id:, user_id: 'me', **)
53
+ response = graph_connection(**).get("#{user_path(user_id)}/drive/items/#{item_id}/content")
54
+ { result: response.body }
55
+ end
56
+
57
+ definition :list_team_drive_items,
58
+ desc: 'List files in a Team\'s SharePoint document library',
59
+ mcp_prefix: 'teams.list_team_drive_items',
60
+ mcp_category: 'teams_files',
61
+ mcp_tier: :standard,
62
+ idempotent: true,
63
+ inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
64
+ trigger_words: %w[sharepoint documents team]
65
+
66
+ def list_team_drive_items(team_id:, **)
67
+ response = graph_connection(**).get("teams/#{team_id}/drive/root/children")
68
+ { result: response.body }
69
+ end
70
+
71
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
72
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -7,6 +7,10 @@ module Legion
7
7
  module MicrosoftTeams
8
8
  module Runners
9
9
  module LocalCache
10
+ definition :extract_local_messages, mcp_exposed: false
11
+ definition :local_cache_available?, mcp_exposed: false
12
+ definition :local_cache_stats, mcp_exposed: false
13
+
10
14
  # Extract messages from the local Teams LevelDB cache.
11
15
  # Works offline without Graph API credentials.
12
16
  def extract_local_messages(db_path: nil, since: nil, channels: nil, senders: nil, skip_bots: true, **)
@@ -8,9 +8,15 @@ module Legion
8
8
  module MicrosoftTeams
9
9
  module Runners
10
10
  module Loop
11
+ extend Legion::Extensions::Definitions
11
12
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
12
13
  include Legion::JSON::Helper
13
14
 
15
+ definition :create_loop_file, mcp_exposed: false
16
+ definition :loop_attachment, mcp_exposed: false
17
+ definition :post_loop_to_chat, mcp_exposed: false
18
+ definition :post_loop_to_channel, mcp_exposed: false
19
+
14
20
  # Creates a new .loop file in the user's OneDrive via the Graph API.
15
21
  # The Fluid Framework collaborative session is initialized by Teams on first open.
16
22
  # Returns the drive item metadata including +webUrl+, which can be passed to
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/microsoft_teams/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module MicrosoftTeams
8
+ module Runners
9
+ module MeetingArtifacts
10
+ include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
+
12
+ def self.trigger_words
13
+ %w[recording recordings whiteboard artifact artifacts]
14
+ end
15
+
16
+ definition :list_meeting_artifacts,
17
+ desc: 'List artifacts (recordings, whiteboards) for an online meeting',
18
+ mcp_prefix: 'teams.list_meeting_artifacts',
19
+ mcp_category: 'teams_meetings',
20
+ mcp_tier: :standard,
21
+ idempotent: true,
22
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
23
+ trigger_words: %w[artifacts recordings whiteboards]
24
+
25
+ def list_meeting_artifacts(meeting_id:, user_id: 'me', **)
26
+ response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/artifacts")
27
+ { result: response.body }
28
+ end
29
+
30
+ definition :get_meeting_artifact,
31
+ desc: 'Get a specific artifact from an online meeting',
32
+ mcp_prefix: 'teams.get_meeting_artifact',
33
+ mcp_category: 'teams_meetings',
34
+ mcp_tier: :standard,
35
+ idempotent: true,
36
+ inputs: { properties: { meeting_id: { type: 'string' },
37
+ artifact_id: { type: 'string' } },
38
+ required: %w[meeting_id artifact_id] },
39
+ trigger_words: %w[artifact recording whiteboard download]
40
+
41
+ def get_meeting_artifact(meeting_id:, artifact_id:, user_id: 'me', **)
42
+ response = graph_connection(**).get(
43
+ "#{user_path(user_id)}/onlineMeetings/#{meeting_id}/artifacts/#{artifact_id}"
44
+ )
45
+ { result: response.body }
46
+ end
47
+
48
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
49
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -9,16 +9,51 @@ module Legion
9
9
  module Meetings
10
10
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
11
 
12
+ def self.trigger_words
13
+ %w[meeting meetings calendar schedule scheduled attendance attendee]
14
+ end
15
+
16
+ definition :list_meetings,
17
+ desc: 'List online meetings for the current user',
18
+ mcp_prefix: 'teams.list_meetings',
19
+ mcp_category: 'teams_meetings',
20
+ mcp_tier: :low,
21
+ idempotent: true,
22
+ trigger_words: %w[meetings upcoming calendar]
23
+
12
24
  def list_meetings(user_id: 'me', **)
13
25
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings")
14
26
  { result: response.body }
15
27
  end
16
28
 
29
+ definition :get_meeting,
30
+ desc: 'Get details for a specific online meeting',
31
+ mcp_prefix: 'teams.get_meeting',
32
+ mcp_category: 'teams_meetings',
33
+ mcp_tier: :low,
34
+ idempotent: true,
35
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
36
+ trigger_words: %w[meeting details]
37
+
17
38
  def get_meeting(meeting_id:, user_id: 'me', **)
18
39
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}")
19
40
  { result: response.body }
20
41
  end
21
42
 
43
+ definition :create_meeting,
44
+ desc: 'Create a new online meeting',
45
+ mcp_prefix: 'teams.create_meeting',
46
+ mcp_category: 'teams_meetings',
47
+ mcp_tier: :elevated,
48
+ idempotent: false,
49
+ inputs: { properties: { subject: { type: 'string' },
50
+ start_time: { type: 'string',
51
+ description: 'ISO 8601 start datetime' },
52
+ end_time: { type: 'string',
53
+ description: 'ISO 8601 end datetime' } },
54
+ required: %w[subject start_time end_time] },
55
+ trigger_words: %w[create schedule]
56
+
22
57
  def create_meeting(subject:, start_time:, end_time:, user_id: 'me', **)
23
58
  payload = {
24
59
  subject: subject,
@@ -29,6 +64,15 @@ module Legion
29
64
  { result: response.body }
30
65
  end
31
66
 
67
+ definition :update_meeting,
68
+ desc: 'Update an existing online meeting',
69
+ mcp_prefix: 'teams.update_meeting',
70
+ mcp_category: 'teams_meetings',
71
+ mcp_tier: :elevated,
72
+ idempotent: false,
73
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
74
+ trigger_words: %w[update reschedule]
75
+
32
76
  def update_meeting(meeting_id:, user_id: 'me', subject: nil, start_time: nil, end_time: nil, **)
33
77
  payload = {}
34
78
  payload[:subject] = subject if subject
@@ -38,27 +82,75 @@ module Legion
38
82
  { result: response.body }
39
83
  end
40
84
 
85
+ definition :delete_meeting,
86
+ desc: 'Delete an online meeting',
87
+ mcp_prefix: 'teams.delete_meeting',
88
+ mcp_category: 'teams_meetings',
89
+ mcp_tier: :high,
90
+ idempotent: false,
91
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
92
+ trigger_words: %w[delete cancel]
93
+
41
94
  def delete_meeting(meeting_id:, user_id: 'me', **)
42
95
  response = graph_connection(**).delete("#{user_path(user_id)}/onlineMeetings/#{meeting_id}")
43
96
  { result: response.body }
44
97
  end
45
98
 
99
+ definition :get_meeting_by_join_url,
100
+ desc: 'Look up a meeting by its join URL',
101
+ mcp_prefix: 'teams.get_meeting_by_join_url',
102
+ mcp_category: 'teams_meetings',
103
+ mcp_tier: :low,
104
+ idempotent: true,
105
+ inputs: { properties: { join_url: { type: 'string',
106
+ description: 'Teams join URL' } },
107
+ required: ['join_url'] },
108
+ trigger_words: %w[url join find]
109
+
46
110
  def get_meeting_by_join_url(join_url:, user_id: 'me', **)
47
111
  params = { '$filter' => "joinWebUrl eq '#{join_url.gsub("'", "''")}'" }
48
112
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings", params)
49
113
  { result: response.body }
50
114
  end
51
115
 
116
+ definition :list_attendance_reports,
117
+ desc: 'List attendance reports for an online meeting',
118
+ mcp_prefix: 'teams.list_attendance_reports',
119
+ mcp_category: 'teams_meetings',
120
+ mcp_tier: :standard,
121
+ idempotent: true,
122
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
123
+ trigger_words: %w[attendance attendees report]
124
+
52
125
  def list_attendance_reports(meeting_id:, user_id: 'me', **)
53
126
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/attendanceReports")
54
127
  { result: response.body }
55
128
  end
56
129
 
130
+ definition :get_attendance_report,
131
+ desc: 'Get a specific attendance report for an online meeting',
132
+ mcp_prefix: 'teams.get_attendance_report',
133
+ mcp_category: 'teams_meetings',
134
+ mcp_tier: :standard,
135
+ idempotent: true,
136
+ inputs: { properties: { meeting_id: { type: 'string' },
137
+ report_id: { type: 'string' } },
138
+ required: %w[meeting_id report_id] },
139
+ trigger_words: %w[attendance details]
140
+
57
141
  def get_attendance_report(meeting_id:, report_id:, user_id: 'me', **)
58
142
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/attendanceReports/#{report_id}")
59
143
  { result: response.body }
60
144
  end
61
145
 
146
+ definition :resolve_meeting,
147
+ desc: 'Resolve a Teams meeting from a chat thread ID or join URL',
148
+ mcp_prefix: 'teams.resolve_meeting',
149
+ mcp_category: 'teams_meetings',
150
+ mcp_tier: :low,
151
+ idempotent: true,
152
+ trigger_words: %w[resolve find]
153
+
62
154
  def resolve_meeting(chat_thread_id: nil, join_url: nil, user_id: 'me', **)
63
155
  return { error: 'provide chat_thread_id or join_url' } unless chat_thread_id || join_url
64
156