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
@@ -7,19 +7,58 @@ module Legion
7
7
  module MicrosoftTeams
8
8
  module Runners
9
9
  module Messages
10
+ extend Legion::Extensions::Definitions
10
11
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
12
 
13
+ def self.trigger_words
14
+ %w[message messages reply replies thread send]
15
+ end
16
+
17
+ definition :list_chat_messages,
18
+ desc: 'List messages in a Teams chat thread',
19
+ mcp_prefix: 'teams.list_chat_messages',
20
+ mcp_category: 'teams_messages',
21
+ mcp_tier: :standard,
22
+ idempotent: true,
23
+ inputs: { properties: { chat_id: { type: 'string',
24
+ description: 'Teams chat ID' } },
25
+ required: ['chat_id'] },
26
+ trigger_words: %w[messages history read]
27
+
12
28
  def list_chat_messages(chat_id:, top: 50, **)
13
29
  params = { '$top' => top }
14
30
  response = graph_connection(**).get("chats/#{chat_id}/messages", params)
15
31
  { result: response.body }
16
32
  end
17
33
 
34
+ definition :get_chat_message,
35
+ desc: 'Get a specific message from a Teams chat',
36
+ mcp_prefix: 'teams.get_chat_message',
37
+ mcp_category: 'teams_messages',
38
+ mcp_tier: :standard,
39
+ idempotent: true,
40
+ inputs: { properties: { chat_id: { type: 'string' },
41
+ message_id: { type: 'string' } },
42
+ required: %w[chat_id message_id] },
43
+ trigger_words: %w[message fetch]
44
+
18
45
  def get_chat_message(chat_id:, message_id:, **)
19
46
  response = graph_connection(**).get("chats/#{chat_id}/messages/#{message_id}")
20
47
  { result: response.body }
21
48
  end
22
49
 
50
+ definition :send_chat_message,
51
+ desc: 'Send a message to a Teams chat',
52
+ mcp_prefix: 'teams.send_chat_message',
53
+ mcp_category: 'teams_messages',
54
+ mcp_tier: :elevated,
55
+ idempotent: false,
56
+ inputs: { properties: { chat_id: { type: 'string' },
57
+ content: { type: 'string',
58
+ description: 'Message text or HTML' } },
59
+ required: %w[chat_id content] },
60
+ trigger_words: %w[send post write]
61
+
23
62
  def send_chat_message(chat_id:, content:, content_type: 'text', attachments: [], **)
24
63
  payload = { body: { contentType: content_type, content: content } }
25
64
  payload[:attachments] = attachments unless attachments.empty?
@@ -27,12 +66,35 @@ module Legion
27
66
  { result: response.body }
28
67
  end
29
68
 
69
+ definition :reply_to_chat_message,
70
+ desc: 'Reply to a message in a Teams chat',
71
+ mcp_prefix: 'teams.reply_to_chat_message',
72
+ mcp_category: 'teams_messages',
73
+ mcp_tier: :elevated,
74
+ idempotent: false,
75
+ inputs: { properties: { chat_id: { type: 'string' },
76
+ message_id: { type: 'string' },
77
+ content: { type: 'string' } },
78
+ required: %w[chat_id message_id content] },
79
+ trigger_words: %w[reply respond]
80
+
30
81
  def reply_to_chat_message(chat_id:, message_id:, content:, content_type: 'text', **)
31
82
  payload = { body: { contentType: content_type, content: content } }
32
83
  response = graph_connection(**).post("chats/#{chat_id}/messages/#{message_id}/replies", payload)
33
84
  { result: response.body }
34
85
  end
35
86
 
87
+ definition :list_message_replies,
88
+ desc: 'List replies to a message in a Teams chat',
89
+ mcp_prefix: 'teams.list_message_replies',
90
+ mcp_category: 'teams_messages',
91
+ mcp_tier: :standard,
92
+ idempotent: true,
93
+ inputs: { properties: { chat_id: { type: 'string' },
94
+ message_id: { type: 'string' } },
95
+ required: %w[chat_id message_id] },
96
+ trigger_words: %w[replies thread]
97
+
36
98
  def list_message_replies(chat_id:, message_id:, top: 50, **)
37
99
  params = { '$top' => top }
38
100
  response = graph_connection(**).get("chats/#{chat_id}/messages/#{message_id}/replies", params)
@@ -13,7 +13,12 @@ module Legion
13
13
  TEAMS_SELECT = 'id,displayName,mail'
14
14
  OWNERS_SELECT = 'id,displayName,mail'
15
15
 
16
+ definition :sync_owners, mcp_exposed: false
17
+ definition :detect_orphans, mcp_exposed: false
18
+ definition :get_team_owners, mcp_exposed: false
19
+
16
20
  def sync_owners(team_id: nil, **)
21
+ log.debug("Ownership#sync_owners team_id=#{team_id.inspect}")
17
22
  conn = graph_connection(**)
18
23
  if team_id
19
24
  owners = fetch_owners(conn: conn, team_id: team_id)
@@ -26,10 +31,12 @@ module Legion
26
31
  { owners: all_owners, team_count: teams.length, synced_at: Time.now.utc.iso8601 }
27
32
  end
28
33
  rescue StandardError => e
34
+ handle_exception(e, level: :error, operation: 'Ownership#sync_owners', team_id: team_id)
29
35
  { error: e.message }
30
36
  end
31
37
 
32
38
  def detect_orphans(**)
39
+ log.debug('Ownership#detect_orphans')
33
40
  conn = graph_connection(**)
34
41
  teams = fetch_all_teams(conn: conn)
35
42
  orphaned = []
@@ -39,16 +46,20 @@ module Legion
39
46
  orphaned << { id: team['id'], display_name: team['displayName'], mail: team['mail'] } if owners.empty?
40
47
  end
41
48
 
49
+ log.info("Ownership#detect_orphans found #{orphaned.length}/#{teams.length} orphaned teams")
42
50
  { orphaned_teams: orphaned, total_scanned: teams.length, orphan_count: orphaned.length }
43
51
  rescue StandardError => e
52
+ handle_exception(e, level: :error, operation: 'Ownership#detect_orphans')
44
53
  { error: e.message }
45
54
  end
46
55
 
47
56
  def get_team_owners(team_id:, **)
57
+ log.debug("Ownership#get_team_owners team_id=#{team_id}")
48
58
  conn = graph_connection(**)
49
59
  owners = fetch_owners(conn: conn, team_id: team_id)
50
60
  { team_id: team_id, owners: owners }
51
61
  rescue StandardError => e
62
+ handle_exception(e, level: :error, operation: 'Ownership#get_team_owners', team_id: team_id)
52
63
  { error: e.message }
53
64
  end
54
65
 
@@ -7,20 +7,45 @@ module Legion
7
7
  module MicrosoftTeams
8
8
  module Runners
9
9
  module People
10
+ extend Legion::Extensions::Definitions
10
11
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
12
 
13
+ def self.trigger_words
14
+ %w[profile people person colleague colleagues contact contacts]
15
+ end
16
+
17
+ definition :get_profile,
18
+ desc: 'Get the Microsoft Graph profile for a user',
19
+ mcp_prefix: 'teams.get_profile',
20
+ mcp_category: 'teams_people',
21
+ mcp_tier: :low,
22
+ idempotent: true,
23
+ trigger_words: %w[profile self]
24
+
12
25
  def get_profile(user_id: 'me', **)
26
+ log.debug("People#get_profile user_id=#{user_id}")
13
27
  response = graph_connection(**).get(user_path(user_id).to_s)
14
28
  { result: response.body }
15
29
  rescue StandardError => e
30
+ handle_exception(e, level: :error, operation: 'People#get_profile', user_id: user_id)
16
31
  { error: e.message }
17
32
  end
18
33
 
34
+ definition :list_people,
35
+ desc: 'List people relevant to the current user (colleagues, contacts)',
36
+ mcp_prefix: 'teams.list_people',
37
+ mcp_category: 'teams_people',
38
+ mcp_tier: :standard,
39
+ idempotent: true,
40
+ trigger_words: %w[people colleagues contacts]
41
+
19
42
  def list_people(user_id: 'me', top: 25, **)
43
+ log.debug("People#list_people user_id=#{user_id} top=#{top}")
20
44
  params = { '$top' => top }
21
45
  response = graph_connection(**).get("#{user_path(user_id)}/people", params)
22
46
  { result: response.body }
23
47
  rescue StandardError => e
48
+ handle_exception(e, level: :error, operation: 'People#list_people', user_id: user_id)
24
49
  { error: e.message }
25
50
  end
26
51
 
@@ -9,7 +9,20 @@ module Legion
9
9
  module Presence
10
10
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
11
 
12
+ def self.trigger_words
13
+ %w[presence availability available status online busy away]
14
+ end
15
+
16
+ definition :get_presence,
17
+ desc: 'Get the presence/availability status for a user',
18
+ mcp_prefix: 'teams.get_presence',
19
+ mcp_category: 'teams_presence',
20
+ mcp_tier: :low,
21
+ idempotent: true,
22
+ trigger_words: %w[presence availability status online]
23
+
12
24
  def get_presence(user_id: 'me', **)
25
+ log.debug("Presence#get_presence user_id=#{user_id}")
13
26
  conn = graph_connection(**)
14
27
  response = conn.get("#{user_path(user_id)}/presence")
15
28
  body = response.body || {}
@@ -19,6 +32,7 @@ module Legion
19
32
  fetched_at: Time.now.utc
20
33
  }
21
34
  rescue StandardError => e
35
+ handle_exception(e, level: :warn, operation: 'Presence#get_presence', user_id: user_id)
22
36
  { availability: 'Offline', activity: 'OffWork', error: e.message, fetched_at: Time.now.utc }
23
37
  end
24
38
 
@@ -11,12 +11,21 @@ module Legion
11
11
  module MicrosoftTeams
12
12
  module Runners
13
13
  module ProfileIngest
14
+ extend Legion::Extensions::Definitions
14
15
  include Helpers::Client
15
16
  include Helpers::PermissionGuard
16
17
  include Helpers::HighWaterMark
17
18
  extend self
18
19
 
20
+ definition :full_ingest, mcp_exposed: false
21
+ definition :ingest_self, mcp_exposed: false
22
+ definition :ingest_people, mcp_exposed: false
23
+ definition :ingest_conversations, mcp_exposed: false
24
+ definition :ingest_teams_and_meetings, mcp_exposed: false
25
+ definition :incremental_sync, mcp_exposed: false
26
+
19
27
  def full_ingest(token:, top_people: 10, message_depth: 50, **)
28
+ log.debug("ProfileIngest#full_ingest top_people=#{top_people} message_depth=#{message_depth}")
20
29
  self_result = ingest_self(token: token)
21
30
  people_result = ingest_people(token: token, top: 25)
22
31
  people = people_result[:skipped] ? [] : (people_result[:people] || [])
@@ -42,7 +51,7 @@ module Legion
42
51
  presence = begin
43
52
  conn.get('me/presence').body
44
53
  rescue StandardError => e
45
- log.debug("ProfileIngest: presence fetch failed: #{e.message}") if defined?(log)
54
+ handle_exception(e, level: :debug, operation: 'ProfileIngest#ingest_self presence') if defined?(handle_exception)
46
55
  {}
47
56
  end
48
57
  unless presence.empty?
@@ -57,6 +66,7 @@ module Legion
57
66
 
58
67
  { profile: profile, presence: presence }
59
68
  rescue StandardError => e
69
+ handle_exception(e, level: :error, operation: 'ProfileIngest#ingest_self')
60
70
  { error: e.message }
61
71
  end
62
72
 
@@ -88,6 +98,7 @@ module Legion
88
98
 
89
99
  { people: people, count: people.length }
90
100
  rescue StandardError => e
101
+ handle_exception(e, level: :error, operation: 'ProfileIngest#ingest_people')
91
102
  { error: e.message, skipped: false }
92
103
  end
93
104
 
@@ -132,6 +143,7 @@ module Legion
132
143
 
133
144
  { ingested: ingested }
134
145
  rescue StandardError => e
146
+ handle_exception(e, level: :error, operation: 'ProfileIngest#ingest_conversations')
135
147
  { error: e.message, ingested: ingested || 0 }
136
148
  end
137
149
 
@@ -177,6 +189,7 @@ module Legion
177
189
 
178
190
  { teams: teams_count, meetings: meetings_count }
179
191
  rescue StandardError => e
192
+ handle_exception(e, level: :error, operation: 'ProfileIngest#ingest_teams_and_meetings')
180
193
  { error: e.message }
181
194
  end
182
195
 
@@ -200,7 +213,7 @@ module Legion
200
213
  members.any? { |m| m['email']&.downcase == email.downcase }
201
214
  end
202
215
  rescue StandardError => e
203
- log.debug("ProfileIngest: find_chat_for_person failed: #{e.message}")
216
+ handle_exception(e, level: :debug, operation: 'ProfileIngest#find_chat_for_person')
204
217
  nil
205
218
  end
206
219
 
@@ -212,7 +225,8 @@ module Legion
212
225
  resp = conn.get("chats/#{chat_id}/messages", params)
213
226
  (resp.body || {}).fetch('value', [])
214
227
  rescue StandardError => e
215
- log.warn("ProfileIngest: fetch_new_messages failed for #{chat_id}: #{e.message}")
228
+ handle_exception(e, level: :warn, operation: 'ProfileIngest#fetch_new_messages',
229
+ chat_id: chat_id)
216
230
  []
217
231
  end
218
232
 
@@ -233,7 +247,7 @@ module Legion
233
247
  llm_ask(message: "#{definition[:prompt]}\n\nConversation with #{peer_name}:\n#{text}")
234
248
  end
235
249
  rescue StandardError => e
236
- log.debug("ProfileIngest: extract_conversation failed: #{e.message}")
250
+ handle_exception(e, level: :debug, operation: 'ProfileIngest#extract_conversation')
237
251
  nil
238
252
  end
239
253
 
@@ -9,16 +9,55 @@ module Legion
9
9
  module Subscriptions
10
10
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
11
 
12
+ def self.trigger_words
13
+ %w[subscription subscriptions webhook webhooks subscribe watch]
14
+ end
15
+
16
+ definition :list_subscriptions,
17
+ desc: 'List active Graph API subscriptions',
18
+ mcp_prefix: 'teams.list_subscriptions',
19
+ mcp_category: 'teams_subscriptions',
20
+ mcp_tier: :standard,
21
+ idempotent: true,
22
+ trigger_words: %w[subscriptions webhooks]
23
+
12
24
  def list_subscriptions(**)
13
25
  response = graph_connection(**).get('subscriptions')
14
26
  { result: response.body }
15
27
  end
16
28
 
29
+ definition :get_subscription,
30
+ desc: 'Get a specific Graph API subscription',
31
+ mcp_prefix: 'teams.get_subscription',
32
+ mcp_category: 'teams_subscriptions',
33
+ mcp_tier: :standard,
34
+ idempotent: true,
35
+ inputs: { properties: { subscription_id: { type: 'string' } },
36
+ required: ['subscription_id'] },
37
+ trigger_words: %w[subscription webhook]
38
+
17
39
  def get_subscription(subscription_id:, **)
18
40
  response = graph_connection(**).get("subscriptions/#{subscription_id}")
19
41
  { result: response.body }
20
42
  end
21
43
 
44
+ definition :create_subscription,
45
+ desc: 'Create a new Graph API change notification subscription',
46
+ mcp_prefix: 'teams.create_subscription',
47
+ mcp_category: 'teams_subscriptions',
48
+ mcp_tier: :elevated,
49
+ idempotent: false,
50
+ inputs: { properties: { resource: { type: 'string',
51
+ description: 'Graph resource path to subscribe to' },
52
+ change_type: { type: 'string',
53
+ description: 'created, updated, deleted' },
54
+ notification_url: { type: 'string',
55
+ description: 'HTTPS webhook URL' },
56
+ expiration: { type: 'string',
57
+ description: 'ISO 8601 expiration datetime' } },
58
+ required: %w[resource change_type notification_url expiration] },
59
+ trigger_words: %w[subscribe webhook create]
60
+
22
61
  def create_subscription(resource:, change_type:, notification_url:, expiration:,
23
62
  client_state: nil, include_resource_data: false, **)
24
63
  payload = {
@@ -33,17 +72,51 @@ module Legion
33
72
  { result: response.body }
34
73
  end
35
74
 
75
+ definition :renew_subscription,
76
+ desc: 'Renew an expiring Graph API subscription',
77
+ mcp_prefix: 'teams.renew_subscription',
78
+ mcp_category: 'teams_subscriptions',
79
+ mcp_tier: :elevated,
80
+ idempotent: false,
81
+ inputs: { properties: { subscription_id: { type: 'string' },
82
+ expiration: { type: 'string',
83
+ description: 'New ISO 8601 expiration datetime' } },
84
+ required: %w[subscription_id expiration] },
85
+ trigger_words: %w[renew extend]
86
+
36
87
  def renew_subscription(subscription_id:, expiration:, **)
37
88
  payload = { expirationDateTime: expiration }
38
89
  response = graph_connection(**).patch("subscriptions/#{subscription_id}", payload)
39
90
  { result: response.body }
40
91
  end
41
92
 
93
+ definition :delete_subscription,
94
+ desc: 'Delete a Graph API subscription',
95
+ mcp_prefix: 'teams.delete_subscription',
96
+ mcp_category: 'teams_subscriptions',
97
+ mcp_tier: :elevated,
98
+ idempotent: false,
99
+ inputs: { properties: { subscription_id: { type: 'string' } },
100
+ required: ['subscription_id'] },
101
+ trigger_words: %w[unsubscribe delete]
102
+
42
103
  def delete_subscription(subscription_id:, **)
43
104
  response = graph_connection(**).delete("subscriptions/#{subscription_id}")
44
105
  { result: response.body }
45
106
  end
46
107
 
108
+ definition :subscribe_to_chat_messages,
109
+ desc: 'Subscribe to new and updated messages in a Teams chat',
110
+ mcp_prefix: 'teams.subscribe_to_chat_messages',
111
+ mcp_category: 'teams_subscriptions',
112
+ mcp_tier: :elevated,
113
+ idempotent: false,
114
+ inputs: { properties: { chat_id: { type: 'string' },
115
+ notification_url: { type: 'string' },
116
+ expiration: { type: 'string' } },
117
+ required: %w[chat_id notification_url expiration] },
118
+ trigger_words: %w[subscribe watch chat]
119
+
47
120
  def subscribe_to_chat_messages(chat_id:, notification_url:, expiration:, client_state: nil, **)
48
121
  create_subscription(
49
122
  resource: "/chats/#{chat_id}/messages",
@@ -55,6 +128,19 @@ module Legion
55
128
  )
56
129
  end
57
130
 
131
+ definition :subscribe_to_channel_messages,
132
+ desc: 'Subscribe to new and updated messages in a Teams channel',
133
+ mcp_prefix: 'teams.subscribe_to_channel_messages',
134
+ mcp_category: 'teams_subscriptions',
135
+ mcp_tier: :elevated,
136
+ idempotent: false,
137
+ inputs: { properties: { team_id: { type: 'string' },
138
+ channel_id: { type: 'string' },
139
+ notification_url: { type: 'string' },
140
+ expiration: { type: 'string' } },
141
+ required: %w[team_id channel_id notification_url expiration] },
142
+ trigger_words: %w[subscribe watch channel]
143
+
58
144
  def subscribe_to_channel_messages(team_id:, channel_id:, notification_url:, expiration:,
59
145
  client_state: nil, **)
60
146
  create_subscription(
@@ -9,16 +9,46 @@ module Legion
9
9
  module Teams
10
10
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
11
11
 
12
+ def self.trigger_words
13
+ %w[team teams workspace group membership]
14
+ end
15
+
16
+ definition :list_joined_teams,
17
+ desc: 'List Teams the current user has joined',
18
+ mcp_prefix: 'teams.list_joined_teams',
19
+ mcp_category: 'teams_teams',
20
+ mcp_tier: :low,
21
+ idempotent: true,
22
+ trigger_words: %w[teams joined membership]
23
+
12
24
  def list_joined_teams(user_id: 'me', **)
13
25
  response = graph_connection(**).get("#{user_path(user_id)}/joinedTeams")
14
26
  { result: response.body }
15
27
  end
16
28
 
29
+ definition :get_team,
30
+ desc: 'Get details for a specific Team by ID',
31
+ mcp_prefix: 'teams.get_team',
32
+ mcp_category: 'teams_teams',
33
+ mcp_tier: :low,
34
+ idempotent: true,
35
+ inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
36
+ trigger_words: %w[team details workspace]
37
+
17
38
  def get_team(team_id:, **)
18
39
  response = graph_connection(**).get("teams/#{team_id}")
19
40
  { result: response.body }
20
41
  end
21
42
 
43
+ definition :list_team_members,
44
+ desc: 'List members of a Team',
45
+ mcp_prefix: 'teams.list_team_members',
46
+ mcp_category: 'teams_teams',
47
+ mcp_tier: :standard,
48
+ idempotent: true,
49
+ inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
50
+ trigger_words: %w[members roster]
51
+
22
52
  def list_team_members(team_id:, **)
23
53
  response = graph_connection(**).get("teams/#{team_id}/members")
24
54
  { result: response.body }
@@ -14,11 +14,35 @@ module Legion
14
14
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
15
15
  }.freeze
16
16
 
17
+ def self.trigger_words
18
+ %w[transcript transcripts vtt spoken]
19
+ end
20
+
21
+ definition :list_transcripts,
22
+ desc: 'List transcripts for an online meeting',
23
+ mcp_prefix: 'teams.list_transcripts',
24
+ mcp_category: 'teams_meetings',
25
+ mcp_tier: :standard,
26
+ idempotent: true,
27
+ inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
28
+ trigger_words: ['transcripts']
29
+
17
30
  def list_transcripts(meeting_id:, user_id: 'me', **)
18
31
  response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/transcripts")
19
32
  { result: response.body }
20
33
  end
21
34
 
35
+ definition :get_transcript,
36
+ desc: 'Get metadata for a specific meeting transcript',
37
+ mcp_prefix: 'teams.get_transcript',
38
+ mcp_category: 'teams_meetings',
39
+ mcp_tier: :standard,
40
+ idempotent: true,
41
+ inputs: { properties: { meeting_id: { type: 'string' },
42
+ transcript_id: { type: 'string' } },
43
+ required: %w[meeting_id transcript_id] },
44
+ trigger_words: %w[transcript details]
45
+
22
46
  def get_transcript(meeting_id:, transcript_id:, user_id: 'me', **)
23
47
  response = graph_connection(**).get(
24
48
  "#{user_path(user_id)}/onlineMeetings/#{meeting_id}/transcripts/#{transcript_id}"
@@ -26,6 +50,17 @@ module Legion
26
50
  { result: response.body }
27
51
  end
28
52
 
53
+ definition :get_transcript_content,
54
+ desc: 'Download the content of a meeting transcript (VTT or DOCX)',
55
+ mcp_prefix: 'teams.get_transcript_content',
56
+ mcp_category: 'teams_meetings',
57
+ mcp_tier: :standard,
58
+ idempotent: true,
59
+ inputs: { properties: { meeting_id: { type: 'string' },
60
+ transcript_id: { type: 'string' } },
61
+ required: %w[meeting_id transcript_id] },
62
+ trigger_words: %w[content vtt text read]
63
+
29
64
  def get_transcript_content(meeting_id:, transcript_id:, user_id: 'me', format: :vtt, **)
30
65
  accept = CONTENT_TYPES.fetch(format)
31
66
  response = graph_connection(**).get(
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MicrosoftTeams
6
- VERSION = '0.6.45'
6
+ VERSION = '0.6.47'
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/extensions/identity/entra/helpers/token_manager'
3
4
  require 'legion/extensions/microsoft_teams/version'
4
5
  require 'legion/extensions/microsoft_teams/helpers/client'
5
6
  require 'legion/extensions/microsoft_teams/runners/auth'
@@ -21,6 +22,11 @@ require 'legion/extensions/microsoft_teams/runners/people'
21
22
  require 'legion/extensions/microsoft_teams/runners/profile_ingest'
22
23
  require 'legion/extensions/microsoft_teams/runners/ownership'
23
24
  require 'legion/extensions/microsoft_teams/runners/api_ingest'
25
+ require 'legion/extensions/microsoft_teams/runners/activities'
26
+ require 'legion/extensions/microsoft_teams/runners/meeting_artifacts'
27
+ require 'legion/extensions/microsoft_teams/runners/call_events'
28
+ require 'legion/extensions/microsoft_teams/runners/app_installations'
29
+ require 'legion/extensions/microsoft_teams/runners/files'
24
30
 
25
31
  # Helpers (bot)
26
32
  require 'legion/extensions/microsoft_teams/helpers/high_water_mark'
@@ -28,9 +34,6 @@ require 'legion/extensions/microsoft_teams/helpers/prompt_resolver'
28
34
  require 'legion/extensions/microsoft_teams/helpers/trace_retriever'
29
35
  require 'legion/extensions/microsoft_teams/helpers/session_manager'
30
36
  require 'legion/extensions/microsoft_teams/helpers/subscription_registry'
31
- require 'legion/extensions/microsoft_teams/helpers/token_cache'
32
- require 'legion/extensions/microsoft_teams/helpers/callback_server'
33
- require 'legion/extensions/microsoft_teams/helpers/browser_auth'
34
37
  require 'legion/extensions/microsoft_teams/helpers/permission_guard'
35
38
  require 'legion/extensions/microsoft_teams/helpers/transform_definitions'
36
39
  require 'legion/extensions/microsoft_teams/helpers/graph_client'
@@ -57,6 +60,10 @@ module Legion
57
60
  module MicrosoftTeams
58
61
  extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
59
62
 
63
+ def self.trigger_words
64
+ %w[teams microsoft_teams microsoftteams microsoft-teams msteams ms-teams]
65
+ end
66
+
60
67
  def self.remote_invocable?
61
68
  false
62
69
  end