lex-microsoft_teams 0.6.49 → 0.6.51

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/lib/legion/extensions/microsoft_teams/actors/api_ingest.rb +8 -13
  4. data/lib/legion/extensions/microsoft_teams/actors/channel_poller.rb +5 -18
  5. data/lib/legion/extensions/microsoft_teams/actors/direct_chat_poller.rb +4 -13
  6. data/lib/legion/extensions/microsoft_teams/actors/incremental_sync.rb +6 -17
  7. data/lib/legion/extensions/microsoft_teams/actors/meeting_ingest.rb +3 -10
  8. data/lib/legion/extensions/microsoft_teams/actors/observed_chat_poller.rb +4 -14
  9. data/lib/legion/extensions/microsoft_teams/actors/presence_poller.rb +3 -6
  10. data/lib/legion/extensions/microsoft_teams/actors/profile_ingest.rb +5 -10
  11. data/lib/legion/extensions/microsoft_teams/errors.rb +84 -0
  12. data/lib/legion/extensions/microsoft_teams/faraday/retry_after.rb +209 -0
  13. data/lib/legion/extensions/microsoft_teams/faraday/throttle_circuit.rb +150 -0
  14. data/lib/legion/extensions/microsoft_teams/helpers/client.rb +80 -3
  15. data/lib/legion/extensions/microsoft_teams/helpers/graph_cache.rb +65 -0
  16. data/lib/legion/extensions/microsoft_teams/helpers/graph_client.rb +20 -0
  17. data/lib/legion/extensions/microsoft_teams/runners/api_ingest.rb +34 -22
  18. data/lib/legion/extensions/microsoft_teams/runners/app_installations.rb +17 -7
  19. data/lib/legion/extensions/microsoft_teams/runners/call_events.rb +70 -11
  20. data/lib/legion/extensions/microsoft_teams/runners/channel_messages.rb +67 -12
  21. data/lib/legion/extensions/microsoft_teams/runners/channels.rb +12 -4
  22. data/lib/legion/extensions/microsoft_teams/runners/chats.rb +42 -6
  23. data/lib/legion/extensions/microsoft_teams/runners/files.rb +72 -9
  24. data/lib/legion/extensions/microsoft_teams/runners/meeting_artifacts.rb +32 -5
  25. data/lib/legion/extensions/microsoft_teams/runners/meetings.rb +34 -4
  26. data/lib/legion/extensions/microsoft_teams/runners/messages.rb +73 -15
  27. data/lib/legion/extensions/microsoft_teams/runners/people.rb +14 -2
  28. data/lib/legion/extensions/microsoft_teams/runners/profile_ingest.rb +23 -11
  29. data/lib/legion/extensions/microsoft_teams/runners/teams.rb +46 -8
  30. data/lib/legion/extensions/microsoft_teams/runners/transcripts.rb +35 -6
  31. data/lib/legion/extensions/microsoft_teams/version.rb +1 -1
  32. data/lib/legion/extensions/microsoft_teams.rb +59 -0
  33. metadata +5 -1
@@ -15,29 +15,39 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_installed_apps_for_user,
18
- desc: 'List Teams apps installed for a user',
18
+ desc: 'List Teams apps installed for a user with expand support',
19
19
  mcp_prefix: 'teams.list_installed_apps_for_user',
20
20
  mcp_category: 'teams_apps',
21
21
  mcp_tier: :low,
22
22
  idempotent: true,
23
+ inputs: { properties: { expand: { type: 'string',
24
+ description: 'Expand related entities (e.g. teamsApp)' } },
25
+ required: [] },
23
26
  trigger_words: %w[apps installed]
24
27
 
25
- def list_installed_apps_for_user(user_id: 'me', **)
26
- response = graph_connection(**).get("#{user_path(user_id)}/teamwork/installedApps")
28
+ def list_installed_apps_for_user(user_id: 'me', expand: nil, **)
29
+ params = {}
30
+ params['$expand'] = expand if expand
31
+ response = graph_connection(**).get("#{user_path(user_id)}/teamwork/installedApps", params)
27
32
  { result: response.body }
28
33
  end
29
34
 
30
35
  definition :list_installed_apps_in_chat,
31
- desc: 'List Teams apps installed in a specific chat',
36
+ desc: 'List Teams apps installed in a specific chat with expand support',
32
37
  mcp_prefix: 'teams.list_installed_apps_in_chat',
33
38
  mcp_category: 'teams_apps',
34
39
  mcp_tier: :low,
35
40
  idempotent: true,
36
- inputs: { properties: { chat_id: { type: 'string' } }, required: ['chat_id'] },
41
+ inputs: { properties: { chat_id: { type: 'string' },
42
+ expand: { type: 'string',
43
+ description: 'Expand related entities (e.g. teamsApp)' } },
44
+ required: ['chat_id'] },
37
45
  trigger_words: %w[apps chat]
38
46
 
39
- def list_installed_apps_in_chat(chat_id:, **)
40
- response = graph_connection(**).get("chats/#{chat_id}/installedApps")
47
+ def list_installed_apps_in_chat(chat_id:, expand: nil, **)
48
+ params = {}
49
+ params['$expand'] = expand if expand
50
+ response = graph_connection(**).get("chats/#{chat_id}/installedApps", params)
41
51
  { result: response.body }
42
52
  end
43
53
 
@@ -15,17 +15,50 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_call_sessions,
18
- desc: 'List sessions for a Teams call record',
18
+ desc: 'List sessions for a Teams call record with pagination and expand',
19
19
  mcp_prefix: 'teams.list_call_sessions',
20
20
  mcp_category: 'teams_calls',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
- inputs: { properties: { call_id: { type: 'string' } }, required: ['call_id'] },
23
+ inputs: { properties: { call_id: { type: 'string' },
24
+ top: { type: 'integer',
25
+ description: 'Sessions per page (default 50)' },
26
+ max_pages: { type: 'integer',
27
+ description: 'Maximum pages to fetch (default 1)' },
28
+ expand: { type: 'string',
29
+ description: 'Expand related entities (e.g. segments)' },
30
+ select: { type: 'string',
31
+ description: 'Comma-separated fields to return' } },
32
+ required: ['call_id'] },
24
33
  trigger_words: %w[sessions calls records]
25
34
 
26
- def list_call_sessions(call_id:, **)
27
- response = graph_connection(**).get("communications/callRecords/#{call_id}/sessions")
28
- { result: response.body }
35
+ def list_call_sessions(call_id:, top: 50, max_pages: 1, expand: nil, select: nil, **)
36
+ params = { '$top' => top }
37
+ params['$expand'] = expand if expand
38
+ params['$select'] = select if select
39
+ conn = graph_connection(**)
40
+ response = conn.get("communications/callRecords/#{call_id}/sessions", params)
41
+ body = response.body
42
+
43
+ return { result: body } if max_pages <= 1
44
+
45
+ all_values = Array(body['value'] || body[:value])
46
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
47
+ pages_fetched = 1
48
+
49
+ while next_link && pages_fetched < max_pages
50
+ response = conn.get(next_link)
51
+ page_body = response.body
52
+ items = page_body['value'] || page_body[:value]
53
+ all_values.concat(Array(items)) if items
54
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
55
+ pages_fetched += 1
56
+ end
57
+
58
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
59
+ 'value' => all_values }
60
+ result['@odata.nextLink'] = next_link if next_link
61
+ { result: result }
29
62
  end
30
63
 
31
64
  definition :get_call_session,
@@ -47,21 +80,47 @@ module Legion
47
80
  end
48
81
 
49
82
  definition :list_session_segments,
50
- desc: 'List segments for a session in a Teams call record',
83
+ desc: 'List segments for a session in a Teams call record with pagination',
51
84
  mcp_prefix: 'teams.list_session_segments',
52
85
  mcp_category: 'teams_calls',
53
86
  mcp_tier: :standard,
54
87
  idempotent: true,
55
88
  inputs: { properties: { call_id: { type: 'string' },
56
- session_id: { type: 'string' } },
89
+ session_id: { type: 'string' },
90
+ top: { type: 'integer',
91
+ description: 'Segments per page (default 50)' },
92
+ max_pages: { type: 'integer',
93
+ description: 'Maximum pages to fetch (default 1)' } },
57
94
  required: %w[call_id session_id] },
58
95
  trigger_words: %w[segments pstn]
59
96
 
60
- def list_session_segments(call_id:, session_id:, **)
61
- response = graph_connection(**).get(
62
- "communications/callRecords/#{call_id}/sessions/#{session_id}/segments"
97
+ def list_session_segments(call_id:, session_id:, top: 50, max_pages: 1, **)
98
+ params = { '$top' => top }
99
+ conn = graph_connection(**)
100
+ response = conn.get(
101
+ "communications/callRecords/#{call_id}/sessions/#{session_id}/segments", params
63
102
  )
64
- { result: response.body }
103
+ body = response.body
104
+
105
+ return { result: body } if max_pages <= 1
106
+
107
+ all_values = Array(body['value'] || body[:value])
108
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
109
+ pages_fetched = 1
110
+
111
+ while next_link && pages_fetched < max_pages
112
+ response = conn.get(next_link)
113
+ page_body = response.body
114
+ items = page_body['value'] || page_body[:value]
115
+ all_values.concat(Array(items)) if items
116
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
117
+ pages_fetched += 1
118
+ end
119
+
120
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
121
+ 'value' => all_values }
122
+ result['@odata.nextLink'] = next_link if next_link
123
+ { result: result }
65
124
  end
66
125
 
67
126
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
@@ -15,20 +15,49 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_channel_messages,
18
- desc: 'List messages posted in a Teams channel',
18
+ desc: 'List messages posted in a Teams channel with pagination and expand support',
19
19
  mcp_prefix: 'teams.list_channel_messages',
20
20
  mcp_category: 'teams_channel_messages',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
23
  inputs: { properties: { team_id: { type: 'string' },
24
- channel_id: { type: 'string' } },
24
+ channel_id: { type: 'string' },
25
+ top: { type: 'integer',
26
+ description: 'Messages per page (default 20, max 50)' },
27
+ max_pages: { type: 'integer',
28
+ description: 'Maximum pages to fetch (default 1)' },
29
+ expand: { type: 'string',
30
+ description: 'Expand related entities (e.g. replies)' } },
25
31
  required: %w[team_id channel_id] },
26
32
  trigger_words: %w[channel history posts feed]
27
33
 
28
- def list_channel_messages(team_id:, channel_id:, top: 50, **)
29
- params = { '$top' => top }
30
- response = graph_connection(**).get("teams/#{team_id}/channels/#{channel_id}/messages", params)
31
- { result: response.body }
34
+ def list_channel_messages(team_id:, channel_id:, top: 50, max_pages: 1, expand: nil, **)
35
+ per_page = [top, 50].min
36
+ params = { '$top' => per_page }
37
+ params['$expand'] = expand if expand
38
+ conn = graph_connection(**)
39
+ response = conn.get("teams/#{team_id}/channels/#{channel_id}/messages", params)
40
+ body = response.body
41
+
42
+ return { result: body } if max_pages <= 1
43
+
44
+ all_values = Array(body['value'] || body[:value])
45
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
46
+ pages_fetched = 1
47
+
48
+ while next_link && pages_fetched < max_pages
49
+ response = conn.get(next_link)
50
+ page_body = response.body
51
+ items = page_body['value'] || page_body[:value]
52
+ all_values.concat(Array(items)) if items
53
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
54
+ pages_fetched += 1
55
+ end
56
+
57
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
58
+ 'value' => all_values }
59
+ result['@odata.nextLink'] = next_link if next_link
60
+ { result: result }
32
61
  end
33
62
 
34
63
  definition :get_channel_message,
@@ -89,23 +118,49 @@ module Legion
89
118
  end
90
119
 
91
120
  definition :list_channel_message_replies,
92
- desc: 'List replies in a Teams channel message thread',
121
+ desc: 'List replies in a Teams channel message thread with pagination',
93
122
  mcp_prefix: 'teams.list_channel_message_replies',
94
123
  mcp_category: 'teams_channel_messages',
95
124
  mcp_tier: :standard,
96
125
  idempotent: true,
97
126
  inputs: { properties: { team_id: { type: 'string' },
98
127
  channel_id: { type: 'string' },
99
- message_id: { type: 'string' } },
128
+ message_id: { type: 'string' },
129
+ top: { type: 'integer',
130
+ description: 'Replies per page (default 50, max 50)' },
131
+ max_pages: { type: 'integer',
132
+ description: 'Maximum pages to fetch (default 1)' } },
100
133
  required: %w[team_id channel_id message_id] },
101
134
  trigger_words: %w[replies thread]
102
135
 
103
- def list_channel_message_replies(team_id:, channel_id:, message_id:, top: 50, **)
104
- params = { '$top' => top }
105
- response = graph_connection(**).get(
136
+ def list_channel_message_replies(team_id:, channel_id:, message_id:, top: 50, max_pages: 1, **)
137
+ per_page = [top, 50].min
138
+ params = { '$top' => per_page }
139
+ conn = graph_connection(**)
140
+ response = conn.get(
106
141
  "teams/#{team_id}/channels/#{channel_id}/messages/#{message_id}/replies", params
107
142
  )
108
- { result: response.body }
143
+ body = response.body
144
+
145
+ return { result: body } if max_pages <= 1
146
+
147
+ all_values = Array(body['value'] || body[:value])
148
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
149
+ pages_fetched = 1
150
+
151
+ while next_link && pages_fetched < max_pages
152
+ response = conn.get(next_link)
153
+ page_body = response.body
154
+ items = page_body['value'] || page_body[:value]
155
+ all_values.concat(Array(items)) if items
156
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
157
+ pages_fetched += 1
158
+ end
159
+
160
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
161
+ 'value' => all_values }
162
+ result['@odata.nextLink'] = next_link if next_link
163
+ { result: result }
109
164
  end
110
165
 
111
166
  definition :edit_channel_message,
@@ -15,16 +15,24 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_channels,
18
- desc: 'List channels in a Team',
18
+ desc: 'List channels in a Team with optional filtering and select',
19
19
  mcp_prefix: 'teams.list_channels',
20
20
  mcp_category: 'teams_channels',
21
21
  mcp_tier: :low,
22
22
  idempotent: true,
23
- inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
23
+ inputs: { properties: { team_id: { type: 'string' },
24
+ filter: { type: 'string',
25
+ description: 'OData $filter (e.g. membershipType eq \'standard\')' },
26
+ select: { type: 'string',
27
+ description: 'Comma-separated fields to return' } },
28
+ required: ['team_id'] },
24
29
  trigger_words: %w[channels list]
25
30
 
26
- def list_channels(team_id:, **)
27
- response = graph_connection(**).get("teams/#{team_id}/channels")
31
+ def list_channels(team_id:, filter: nil, select: nil, **)
32
+ params = {}
33
+ params['$filter'] = filter if filter
34
+ params['$select'] = select if select
35
+ response = graph_connection(**).get("teams/#{team_id}/channels", params)
28
36
  { result: response.body }
29
37
  end
30
38
 
@@ -15,18 +15,54 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_chats,
18
- desc: 'List Teams chats for the current user',
18
+ desc: 'List Teams chats for the current user with pagination, filtering, and expand support',
19
19
  mcp_prefix: 'teams.list_chats',
20
20
  mcp_category: 'teams_chat',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
+ inputs: { properties: { top: { type: 'integer',
24
+ description: 'Number of chats per page (default 50, max 50)' },
25
+ max_pages: { type: 'integer',
26
+ description: 'Maximum pages to fetch (default 1)' },
27
+ expand: { type: 'string',
28
+ description: 'Expand related entities: members, lastMessagePreview' },
29
+ filter: { type: 'string',
30
+ description: 'OData $filter expression (e.g. chatType eq \'group\')' },
31
+ orderby: { type: 'string',
32
+ description: 'Sort order (e.g. lastMessagePreview/createdDateTime desc)' } },
33
+ required: [] },
23
34
  trigger_words: %w[chats conversations]
24
35
 
25
- def list_chats(user_id: 'me', top: 50, **)
26
- log.debug "list_chats(user_id: #{user_id}, top: #{top})"
27
- params = { '$top' => top }
28
- response = graph_connection(**).get("#{user_path(user_id)}/chats", params)
29
- { result: response.body }
36
+ def list_chats(user_id: 'me', top: 50, max_pages: 1, expand: nil, filter: nil, orderby: nil, **)
37
+ log.debug "list_chats(user_id: #{user_id}, top: #{top}, max_pages: #{max_pages})"
38
+ per_page = [top, 50].min
39
+ params = { '$top' => per_page }
40
+ params['$expand'] = expand if expand
41
+ params['$filter'] = filter if filter
42
+ params['$orderby'] = orderby if orderby
43
+ conn = graph_connection(**)
44
+ response = conn.get("#{user_path(user_id)}/chats", params)
45
+ body = response.body
46
+
47
+ return { result: body } if max_pages <= 1
48
+
49
+ all_values = Array(body['value'] || body[:value])
50
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
51
+ pages_fetched = 1
52
+
53
+ while next_link && pages_fetched < max_pages
54
+ response = conn.get(next_link)
55
+ page_body = response.body
56
+ items = page_body['value'] || page_body[:value]
57
+ all_values.concat(Array(items)) if items
58
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
59
+ pages_fetched += 1
60
+ end
61
+
62
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
63
+ 'value' => all_values }
64
+ result['@odata.nextLink'] = next_link if next_link
65
+ { result: result }
30
66
  end
31
67
 
32
68
  definition :get_chat,
@@ -15,17 +15,50 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_drive_items,
18
- desc: 'List files in the root of a user\'s OneDrive',
18
+ desc: 'List files in the root of a user\'s OneDrive with pagination and filtering',
19
19
  mcp_prefix: 'teams.list_drive_items',
20
20
  mcp_category: 'teams_files',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
+ inputs: { properties: { top: { type: 'integer',
24
+ description: 'Items per page (default 200)' },
25
+ max_pages: { type: 'integer',
26
+ description: 'Maximum pages to fetch (default 1)' },
27
+ select: { type: 'string',
28
+ description: 'Comma-separated fields to return' },
29
+ filter: { type: 'string',
30
+ description: 'OData $filter expression' } },
31
+ required: [] },
23
32
  trigger_words: %w[files drive onedrive]
24
33
 
25
- def list_drive_items(user_id: 'me', **)
34
+ def list_drive_items(user_id: 'me', top: 200, max_pages: 1, select: nil, filter: nil, **)
26
35
  log.debug "list_drive_items(user_id: #{user_id})"
27
- response = graph_connection(**).get("#{user_path(user_id)}/drive/root/children")
28
- { result: response.body }
36
+ params = { '$top' => top }
37
+ params['$select'] = select if select
38
+ params['$filter'] = filter if filter
39
+ conn = graph_connection(**)
40
+ response = conn.get("#{user_path(user_id)}/drive/root/children", params)
41
+ body = response.body
42
+
43
+ return { result: body } if max_pages <= 1
44
+
45
+ all_values = Array(body['value'] || body[:value])
46
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
47
+ pages_fetched = 1
48
+
49
+ while next_link && pages_fetched < max_pages
50
+ response = conn.get(next_link)
51
+ page_body = response.body
52
+ items = page_body['value'] || page_body[:value]
53
+ all_values.concat(Array(items)) if items
54
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
55
+ pages_fetched += 1
56
+ end
57
+
58
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
59
+ 'value' => all_values }
60
+ result['@odata.nextLink'] = next_link if next_link
61
+ { result: result }
29
62
  end
30
63
 
31
64
  definition :get_drive_item,
@@ -59,18 +92,48 @@ module Legion
59
92
  end
60
93
 
61
94
  definition :list_team_drive_items,
62
- desc: 'List files in a Team\'s SharePoint document library',
95
+ desc: 'List files in a Team\'s SharePoint document library with pagination',
63
96
  mcp_prefix: 'teams.list_team_drive_items',
64
97
  mcp_category: 'teams_files',
65
98
  mcp_tier: :standard,
66
99
  idempotent: true,
67
- inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
100
+ inputs: { properties: { team_id: { type: 'string' },
101
+ top: { type: 'integer',
102
+ description: 'Items per page (default 200)' },
103
+ max_pages: { type: 'integer',
104
+ description: 'Maximum pages to fetch (default 1)' },
105
+ select: { type: 'string',
106
+ description: 'Comma-separated fields to return' } },
107
+ required: ['team_id'] },
68
108
  trigger_words: %w[sharepoint documents team]
69
109
 
70
- def list_team_drive_items(team_id:, **)
110
+ def list_team_drive_items(team_id:, top: 200, max_pages: 1, select: nil, **)
71
111
  log.debug "list_team_drive_items(team_id: #{team_id})"
72
- response = graph_connection(**).get("teams/#{team_id}/drive/root/children")
73
- { result: response.body }
112
+ params = { '$top' => top }
113
+ params['$select'] = select if select
114
+ conn = graph_connection(**)
115
+ response = conn.get("teams/#{team_id}/drive/root/children", params)
116
+ body = response.body
117
+
118
+ return { result: body } if max_pages <= 1
119
+
120
+ all_values = Array(body['value'] || body[:value])
121
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
122
+ pages_fetched = 1
123
+
124
+ while next_link && pages_fetched < max_pages
125
+ response = conn.get(next_link)
126
+ page_body = response.body
127
+ items = page_body['value'] || page_body[:value]
128
+ all_values.concat(Array(items)) if items
129
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
130
+ pages_fetched += 1
131
+ end
132
+
133
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
134
+ 'value' => all_values }
135
+ result['@odata.nextLink'] = next_link if next_link
136
+ { result: result }
74
137
  end
75
138
 
76
139
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
@@ -15,17 +15,44 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_meeting_artifacts,
18
- desc: 'List artifacts (recordings, whiteboards) for an online meeting',
18
+ desc: 'List artifacts (recordings, whiteboards) for an online meeting with pagination',
19
19
  mcp_prefix: 'teams.list_meeting_artifacts',
20
20
  mcp_category: 'teams_meetings',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
- inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
23
+ inputs: { properties: { meeting_id: { type: 'string' },
24
+ top: { type: 'integer',
25
+ description: 'Artifacts per page (default 50)' },
26
+ max_pages: { type: 'integer',
27
+ description: 'Maximum pages to fetch (default 1)' } },
28
+ required: ['meeting_id'] },
24
29
  trigger_words: %w[artifacts recordings whiteboards]
25
30
 
26
- def list_meeting_artifacts(meeting_id:, user_id: 'me', **)
27
- response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/artifacts")
28
- { result: response.body }
31
+ def list_meeting_artifacts(meeting_id:, user_id: 'me', top: 50, max_pages: 1, **)
32
+ params = { '$top' => top }
33
+ conn = graph_connection(**)
34
+ response = conn.get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/artifacts", params)
35
+ body = response.body
36
+
37
+ return { result: body } if max_pages <= 1
38
+
39
+ all_values = Array(body['value'] || body[:value])
40
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
41
+ pages_fetched = 1
42
+
43
+ while next_link && pages_fetched < max_pages
44
+ response = conn.get(next_link)
45
+ page_body = response.body
46
+ items = page_body['value'] || page_body[:value]
47
+ all_values.concat(Array(items)) if items
48
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
49
+ pages_fetched += 1
50
+ end
51
+
52
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
53
+ 'value' => all_values }
54
+ result['@odata.nextLink'] = next_link if next_link
55
+ { result: result }
29
56
  end
30
57
 
31
58
  definition :get_meeting_artifact,
@@ -15,16 +15,46 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_meetings,
18
- desc: 'List online meetings for the current user',
18
+ desc: 'List online meetings for the current user with pagination and filtering',
19
19
  mcp_prefix: 'teams.list_meetings',
20
20
  mcp_category: 'teams_meetings',
21
21
  mcp_tier: :low,
22
22
  idempotent: true,
23
+ inputs: { properties: { top: { type: 'integer',
24
+ description: 'Meetings per page (default 50)' },
25
+ max_pages: { type: 'integer',
26
+ description: 'Maximum pages to fetch (default 1)' },
27
+ filter: { type: 'string',
28
+ description: 'OData $filter expression' } },
29
+ required: [] },
23
30
  trigger_words: %w[meetings upcoming calendar]
24
31
 
25
- def list_meetings(user_id: 'me', **)
26
- response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings")
27
- { result: response.body }
32
+ def list_meetings(user_id: 'me', top: 50, max_pages: 1, filter: nil, **)
33
+ params = { '$top' => top }
34
+ params['$filter'] = filter if filter
35
+ conn = graph_connection(**)
36
+ response = conn.get("#{user_path(user_id)}/onlineMeetings", params)
37
+ body = response.body
38
+
39
+ return { result: body } if max_pages <= 1
40
+
41
+ all_values = Array(body['value'] || body[:value])
42
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
43
+ pages_fetched = 1
44
+
45
+ while next_link && pages_fetched < max_pages
46
+ response = conn.get(next_link)
47
+ page_body = response.body
48
+ items = page_body['value'] || page_body[:value]
49
+ all_values.concat(Array(items)) if items
50
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
51
+ pages_fetched += 1
52
+ end
53
+
54
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
55
+ 'value' => all_values }
56
+ result['@odata.nextLink'] = next_link if next_link
57
+ { result: result }
28
58
  end
29
59
 
30
60
  definition :get_meeting,