lex-microsoft_teams 0.6.49 → 0.6.50

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d7ed7b213d241bf12a0a136a8dc25f2947bc9ca3ddd12a9d82209ba48f32a09
4
- data.tar.gz: d95e3207bd11005fc666fe9b041af5a5f5293258b61e66c178d0ce6d14180621
3
+ metadata.gz: 35010357ecbf55560b6957ae2467c5975eec04056da102a34b1d4c870a0f1ce3
4
+ data.tar.gz: 38d375c241ba2691948bf6d80f275410f0a1003e0e7456852f3bc2ca1bf4acce
5
5
  SHA512:
6
- metadata.gz: 69406bc97f76d99714cf2956b42f60f86554878139f7ff2c1af43de259d295c5de2fc0b2440ed77476e5596d1514f7a524109a88baa5315de55d927c6a83f7e7
7
- data.tar.gz: 4dc517d8e65e5fbed9e685c17e3966da84030a0b5a51087e391903720c3ee49097ca041d83fd1062dad40e4825805d156685cd617305ffe8ca70ca10abb06564
6
+ metadata.gz: ae71a656141975e85dc7b5e294e99d7d5a1f67a34e57feea943f177d4bf1c31224ef462adc5140163f6cb028ca1fede816fa713c25185522d8718fe01631a0af
7
+ data.tar.gz: b2e865c65531f7d464d7f058c2e8d965b1ca96643ca15bee7a6018c0dd260d7477771b0418404f9a6dd0ef8fc8dd983d8c68d0a06704fb2432af031d377150d6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.50] - 2026-05-27
4
+ ### Added
5
+ - Full OData query parameter support across all Graph API runner methods per Microsoft Graph REST v1.0 docs
6
+ - `max_pages` pagination parameter on all list endpoints — follows `@odata.nextLink` automatically to fetch multiple pages in a single call
7
+ - `$top` exposed in MCP inputs for: list_chat_messages, list_chats, list_channel_messages, list_channel_message_replies, list_message_replies, list_team_members, list_meetings, list_drive_items, list_team_drive_items, list_call_sessions, list_session_segments, list_meeting_artifacts, list_transcripts
8
+ - `$orderby` support for list_chat_messages (lastModifiedDateTime desc, createdDateTime desc) and list_chats (lastMessagePreview/createdDateTime desc)
9
+ - `$filter` support for list_chat_messages, list_chats, list_channels, list_joined_teams, list_team_members, list_meetings, list_drive_items, list_people
10
+ - `$expand` support for list_chats (members, lastMessagePreview), list_channel_messages (replies), list_installed_apps_for_user, list_installed_apps_in_chat, list_call_sessions (segments)
11
+ - `$select` support for list_channels, list_joined_teams, list_drive_items, list_team_drive_items, list_call_sessions, list_people
12
+ - `$search` support for list_people
13
+ - `format` (vtt/docx) exposed in MCP inputs for get_transcript_content
14
+
15
+ ### Fixed
16
+ - Per-page size capped at Graph API maximum (50 for messages/chats, 200 for drive items) regardless of `top` value passed
17
+
3
18
  ## [0.6.48] - 2026-05-18
4
19
  ### Added
5
20
  - Definition DSL declarations across all runners for proper tool discovery and MCP exposure
@@ -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,
@@ -15,21 +15,53 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_chat_messages,
18
- desc: 'List messages in a Teams chat thread',
18
+ desc: 'List messages in a Teams chat thread with pagination, ordering, and filtering',
19
19
  mcp_prefix: 'teams.list_chat_messages',
20
20
  mcp_category: 'teams_messages',
21
21
  mcp_tier: :standard,
22
22
  idempotent: true,
23
- inputs: { properties: { chat_id: { type: 'string',
24
- description: 'Teams chat ID' } },
23
+ inputs: { properties: { chat_id: { type: 'string',
24
+ description: 'Teams chat ID' },
25
+ top: { type: 'integer',
26
+ description: 'Messages per page (default 50, max 50)' },
27
+ max_pages: { type: 'integer',
28
+ description: 'Maximum pages to fetch (default 1)' },
29
+ orderby: { type: 'string',
30
+ description: 'Sort order: lastModifiedDateTime desc or createdDateTime desc' },
31
+ filter: { type: 'string',
32
+ description: 'OData $filter on lastModifiedDateTime or createdDateTime' } },
25
33
  required: ['chat_id'] },
26
34
  trigger_words: %w[messages history read]
27
35
 
28
- def list_chat_messages(chat_id:, top: 50, **)
29
- log.debug "list_chat_messages(chat_id: #{chat_id}, top: #{top})"
30
- params = { '$top' => top }
31
- response = graph_connection(**).get("chats/#{chat_id}/messages", params)
32
- { result: response.body }
36
+ def list_chat_messages(chat_id:, top: 50, max_pages: 1, orderby: nil, filter: nil, **)
37
+ log.debug "list_chat_messages(chat_id: #{chat_id}, top: #{top}, max_pages: #{max_pages})"
38
+ per_page = [top, 50].min
39
+ params = { '$top' => per_page }
40
+ params['$orderby'] = orderby if orderby
41
+ params['$filter'] = filter if filter
42
+ conn = graph_connection(**)
43
+ response = conn.get("chats/#{chat_id}/messages", params)
44
+ body = response.body
45
+
46
+ return { result: body } if max_pages <= 1
47
+
48
+ all_values = Array(body['value'] || body[:value])
49
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
50
+ pages_fetched = 1
51
+
52
+ while next_link && pages_fetched < max_pages
53
+ response = conn.get(next_link)
54
+ page_body = response.body
55
+ items = page_body['value'] || page_body[:value]
56
+ all_values.concat(Array(items)) if items
57
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
58
+ pages_fetched += 1
59
+ end
60
+
61
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
62
+ 'value' => all_values }
63
+ result['@odata.nextLink'] = next_link if next_link
64
+ { result: result }
33
65
  end
34
66
 
35
67
  definition :get_chat_message,
@@ -89,21 +121,47 @@ module Legion
89
121
  end
90
122
 
91
123
  definition :list_message_replies,
92
- desc: 'List replies to a message in a Teams chat',
124
+ desc: 'List replies to a message in a Teams chat with pagination support',
93
125
  mcp_prefix: 'teams.list_message_replies',
94
126
  mcp_category: 'teams_messages',
95
127
  mcp_tier: :standard,
96
128
  idempotent: true,
97
129
  inputs: { properties: { chat_id: { type: 'string' },
98
- message_id: { type: 'string' } },
130
+ message_id: { type: 'string' },
131
+ top: { type: 'integer',
132
+ description: 'Number of replies to return per page (default 50)' },
133
+ max_pages: { type: 'integer',
134
+ description: 'Maximum pages to fetch (default 1)' } },
99
135
  required: %w[chat_id message_id] },
100
136
  trigger_words: %w[replies thread]
101
137
 
102
- def list_message_replies(chat_id:, message_id:, top: 50, **)
103
- log.debug "list_message_replies(chat_id: #{chat_id}, message_id: #{message_id}, top: #{top})"
104
- params = { '$top' => top }
105
- response = graph_connection(**).get("chats/#{chat_id}/messages/#{message_id}/replies", params)
106
- { result: response.body }
138
+ def list_message_replies(chat_id:, message_id:, top: 50, max_pages: 1, **)
139
+ log.debug "list_message_replies(chat_id: #{chat_id}, message_id: #{message_id}, top: #{top}, max_pages: #{max_pages})"
140
+ per_page = [top, 50].min
141
+ params = { '$top' => per_page }
142
+ conn = graph_connection(**)
143
+ response = conn.get("chats/#{chat_id}/messages/#{message_id}/replies", params)
144
+ body = response.body
145
+
146
+ return { result: body } if max_pages <= 1
147
+
148
+ all_values = Array(body['value'] || body[:value])
149
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
150
+ pages_fetched = 1
151
+
152
+ while next_link && pages_fetched < max_pages
153
+ response = conn.get(next_link)
154
+ page_body = response.body
155
+ items = page_body['value'] || page_body[:value]
156
+ all_values.concat(Array(items)) if items
157
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
158
+ pages_fetched += 1
159
+ end
160
+
161
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
162
+ 'value' => all_values }
163
+ result['@odata.nextLink'] = next_link if next_link
164
+ { result: result }
107
165
  end
108
166
 
109
167
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
@@ -32,16 +32,28 @@ module Legion
32
32
  end
33
33
 
34
34
  definition :list_people,
35
- desc: 'List people relevant to the current user (colleagues, contacts)',
35
+ desc: 'List people relevant to the current user with search and filter support',
36
36
  mcp_prefix: 'teams.list_people',
37
37
  mcp_category: 'teams_people',
38
38
  mcp_tier: :standard,
39
39
  idempotent: true,
40
+ inputs: { properties: { top: { type: 'integer',
41
+ description: 'Number of people to return (default 25)' },
42
+ search: { type: 'string',
43
+ description: 'Search term to find people by name or email' },
44
+ filter: { type: 'string',
45
+ description: 'OData $filter expression' },
46
+ select: { type: 'string',
47
+ description: 'Comma-separated fields to return' } },
48
+ required: [] },
40
49
  trigger_words: %w[people colleagues contacts]
41
50
 
42
- def list_people(user_id: 'me', top: 25, **)
51
+ def list_people(user_id: 'me', top: 25, search: nil, filter: nil, select: nil, **)
43
52
  log.debug("People#list_people user_id=#{user_id} top=#{top}")
44
53
  params = { '$top' => top }
54
+ params['$search'] = "\"#{search}\"" if search
55
+ params['$filter'] = filter if filter
56
+ params['$select'] = select if select
45
57
  response = graph_connection(**).get("#{user_path(user_id)}/people", params)
46
58
  { result: response.body }
47
59
  rescue StandardError => e
@@ -15,15 +15,23 @@ module Legion
15
15
  end
16
16
 
17
17
  definition :list_joined_teams,
18
- desc: 'List Teams the current user has joined',
18
+ desc: 'List Teams the current user has joined with optional filtering and select',
19
19
  mcp_prefix: 'teams.list_joined_teams',
20
20
  mcp_category: 'teams_teams',
21
21
  mcp_tier: :low,
22
22
  idempotent: true,
23
+ inputs: { properties: { filter: { type: 'string',
24
+ description: 'OData $filter expression' },
25
+ select: { type: 'string',
26
+ description: 'Comma-separated fields to return' } },
27
+ required: [] },
23
28
  trigger_words: %w[teams joined membership]
24
29
 
25
- def list_joined_teams(user_id: 'me', **)
26
- response = graph_connection(**).get("#{user_path(user_id)}/joinedTeams")
30
+ def list_joined_teams(user_id: 'me', filter: nil, select: nil, **)
31
+ params = {}
32
+ params['$filter'] = filter if filter
33
+ params['$select'] = select if select
34
+ response = graph_connection(**).get("#{user_path(user_id)}/joinedTeams", params)
27
35
  { result: response.body }
28
36
  end
29
37
 
@@ -42,17 +50,47 @@ module Legion
42
50
  end
43
51
 
44
52
  definition :list_team_members,
45
- desc: 'List members of a Team',
53
+ desc: 'List members of a Team with pagination',
46
54
  mcp_prefix: 'teams.list_team_members',
47
55
  mcp_category: 'teams_teams',
48
56
  mcp_tier: :standard,
49
57
  idempotent: true,
50
- inputs: { properties: { team_id: { type: 'string' } }, required: ['team_id'] },
58
+ inputs: { properties: { team_id: { type: 'string' },
59
+ top: { type: 'integer',
60
+ description: 'Members per page (default 100)' },
61
+ max_pages: { type: 'integer',
62
+ description: 'Maximum pages to fetch (default 1)' },
63
+ filter: { type: 'string',
64
+ description: 'OData $filter expression' } },
65
+ required: ['team_id'] },
51
66
  trigger_words: %w[members roster]
52
67
 
53
- def list_team_members(team_id:, **)
54
- response = graph_connection(**).get("teams/#{team_id}/members")
55
- { result: response.body }
68
+ def list_team_members(team_id:, top: 100, max_pages: 1, filter: nil, **)
69
+ params = { '$top' => top }
70
+ params['$filter'] = filter if filter
71
+ conn = graph_connection(**)
72
+ response = conn.get("teams/#{team_id}/members", params)
73
+ body = response.body
74
+
75
+ return { result: body } if max_pages <= 1
76
+
77
+ all_values = Array(body['value'] || body[:value])
78
+ next_link = body['@odata.nextLink'] || body[:'@odata.nextLink']
79
+ pages_fetched = 1
80
+
81
+ while next_link && pages_fetched < max_pages
82
+ response = conn.get(next_link)
83
+ page_body = response.body
84
+ items = page_body['value'] || page_body[:value]
85
+ all_values.concat(Array(items)) if items
86
+ next_link = page_body['@odata.nextLink'] || page_body[:'@odata.nextLink']
87
+ pages_fetched += 1
88
+ end
89
+
90
+ result = { '@odata.context' => body['@odata.context'] || body[:'@odata.context'],
91
+ 'value' => all_values }
92
+ result['@odata.nextLink'] = next_link if next_link
93
+ { result: result }
56
94
  end
57
95
 
58
96
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
@@ -20,17 +20,44 @@ module Legion
20
20
  end
21
21
 
22
22
  definition :list_transcripts,
23
- desc: 'List transcripts for an online meeting',
23
+ desc: 'List transcripts for an online meeting with pagination',
24
24
  mcp_prefix: 'teams.list_transcripts',
25
25
  mcp_category: 'teams_meetings',
26
26
  mcp_tier: :standard,
27
27
  idempotent: true,
28
- inputs: { properties: { meeting_id: { type: 'string' } }, required: ['meeting_id'] },
28
+ inputs: { properties: { meeting_id: { type: 'string' },
29
+ top: { type: 'integer',
30
+ description: 'Transcripts per page (default 50)' },
31
+ max_pages: { type: 'integer',
32
+ description: 'Maximum pages to fetch (default 1)' } },
33
+ required: ['meeting_id'] },
29
34
  trigger_words: ['transcripts']
30
35
 
31
- def list_transcripts(meeting_id:, user_id: 'me', **)
32
- response = graph_connection(**).get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/transcripts")
33
- { result: response.body }
36
+ def list_transcripts(meeting_id:, user_id: 'me', top: 50, max_pages: 1, **)
37
+ params = { '$top' => top }
38
+ conn = graph_connection(**)
39
+ response = conn.get("#{user_path(user_id)}/onlineMeetings/#{meeting_id}/transcripts", 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 }
34
61
  end
35
62
 
36
63
  definition :get_transcript,
@@ -58,7 +85,9 @@ module Legion
58
85
  mcp_tier: :standard,
59
86
  idempotent: true,
60
87
  inputs: { properties: { meeting_id: { type: 'string' },
61
- transcript_id: { type: 'string' } },
88
+ transcript_id: { type: 'string' },
89
+ format: { type: 'string',
90
+ description: 'Output format: vtt (default) or docx' } },
62
91
  required: %w[meeting_id transcript_id] },
63
92
  trigger_words: %w[content vtt text read]
64
93
 
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MicrosoftTeams
6
- VERSION = '0.6.49'
6
+ VERSION = '0.6.50'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-microsoft_teams
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.49
4
+ version: 0.6.50
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity