uk-parliament-mcp 1.0.0__py3-none-any.whl
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.
- uk_parliament_mcp/__init__.py +3 -0
- uk_parliament_mcp/__main__.py +22 -0
- uk_parliament_mcp/http_client.py +159 -0
- uk_parliament_mcp/server.py +42 -0
- uk_parliament_mcp/tools/__init__.py +1 -0
- uk_parliament_mcp/tools/bills.py +385 -0
- uk_parliament_mcp/tools/committees.py +385 -0
- uk_parliament_mcp/tools/commons_votes.py +138 -0
- uk_parliament_mcp/tools/core.py +28 -0
- uk_parliament_mcp/tools/erskine_may.py +25 -0
- uk_parliament_mcp/tools/hansard.py +39 -0
- uk_parliament_mcp/tools/interests.py +43 -0
- uk_parliament_mcp/tools/lords_votes.py +149 -0
- uk_parliament_mcp/tools/members.py +439 -0
- uk_parliament_mcp/tools/now.py +30 -0
- uk_parliament_mcp/tools/oral_questions.py +55 -0
- uk_parliament_mcp/tools/statutory_instruments.py +38 -0
- uk_parliament_mcp/tools/treaties.py +25 -0
- uk_parliament_mcp/tools/whatson.py +72 -0
- uk_parliament_mcp-1.0.0.dist-info/METADATA +379 -0
- uk_parliament_mcp-1.0.0.dist-info/RECORD +23 -0
- uk_parliament_mcp-1.0.0.dist-info/WHEEL +4 -0
- uk_parliament_mcp-1.0.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Lords Votes API tools for House of Lords divisions and voting records."""
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from mcp.server.fastmcp import FastMCP
|
|
5
|
+
|
|
6
|
+
from uk_parliament_mcp.http_client import build_url, get_result
|
|
7
|
+
|
|
8
|
+
LORDS_VOTES_API_BASE = "http://lordsvotes-api.parliament.uk/data"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_tools(mcp: FastMCP) -> None:
|
|
12
|
+
"""Register Lords votes tools with the MCP server."""
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
async def search_lords_divisions(search_term: str) -> str:
|
|
16
|
+
"""Search House of Lords voting records (divisions). Use when you want to find how Lords voted on specific issues, bills, or amendments in the upper chamber.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
search_term: Search term for Lords division topics (e.g. 'brexit', 'climate', 'NHS').
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Lords divisions matching the search term.
|
|
23
|
+
"""
|
|
24
|
+
url = f"{LORDS_VOTES_API_BASE}/divisions/search?queryParameters.searchTerm={quote(search_term)}"
|
|
25
|
+
return await get_result(url)
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
async def get_lords_voting_record_for_member(
|
|
29
|
+
member_id: int,
|
|
30
|
+
search_term: str | None = None,
|
|
31
|
+
include_when_member_was_teller: bool | None = None,
|
|
32
|
+
start_date: str | None = None,
|
|
33
|
+
end_date: str | None = None,
|
|
34
|
+
division_number: int | None = None,
|
|
35
|
+
skip: int = 0,
|
|
36
|
+
take: int = 25,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""Get complete voting record of a Lord in House of Lords divisions. Use when analyzing how a specific Lord votes, their voting patterns, or their stance on particular issues through their voting history.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
member_id: Parliament member ID to get Lords voting record for.
|
|
42
|
+
search_term: Optional: search term to filter divisions.
|
|
43
|
+
include_when_member_was_teller: Optional: include votes where member was a teller.
|
|
44
|
+
start_date: Optional: start date in YYYY-MM-DD format.
|
|
45
|
+
end_date: Optional: end date in YYYY-MM-DD format.
|
|
46
|
+
division_number: Optional: specific division number.
|
|
47
|
+
skip: Number of records to skip (for pagination).
|
|
48
|
+
take: Number of records to return (default 25, max 100).
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Complete Lords voting record for the member.
|
|
52
|
+
"""
|
|
53
|
+
url = build_url(
|
|
54
|
+
f"{LORDS_VOTES_API_BASE}/Divisions/membervoting",
|
|
55
|
+
{
|
|
56
|
+
"MemberId": member_id,
|
|
57
|
+
"SearchTerm": search_term,
|
|
58
|
+
"IncludeWhenMemberWasTeller": include_when_member_was_teller,
|
|
59
|
+
"StartDate": start_date,
|
|
60
|
+
"EndDate": end_date,
|
|
61
|
+
"DivisionNumber": division_number,
|
|
62
|
+
"skip": skip,
|
|
63
|
+
"take": take,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
return await get_result(url)
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
async def get_lords_division_by_id(division_id: int) -> str:
|
|
70
|
+
"""Get detailed information about a specific House of Lords division by ID. Use when you need complete details about a particular Lords vote including who voted content/not content, tellers, and voting totals.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
division_id: Unique Lords division ID number.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Detailed information about the Lords division.
|
|
77
|
+
"""
|
|
78
|
+
url = f"{LORDS_VOTES_API_BASE}/Divisions/{division_id}"
|
|
79
|
+
return await get_result(url)
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
async def get_lords_divisions_grouped_by_party(
|
|
83
|
+
search_term: str | None = None,
|
|
84
|
+
member_id: int | None = None,
|
|
85
|
+
start_date: str | None = None,
|
|
86
|
+
end_date: str | None = None,
|
|
87
|
+
division_number: int | None = None,
|
|
88
|
+
include_when_member_was_teller: bool | None = None,
|
|
89
|
+
) -> str:
|
|
90
|
+
"""Get House of Lords divisions grouped by party voting patterns. Use when analyzing how different parties voted on issues in the Lords or understanding party-line voting behavior. Shows vote counts by party rather than individual Lords.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
search_term: Optional: search term to filter divisions.
|
|
94
|
+
member_id: Optional: member ID to filter divisions.
|
|
95
|
+
start_date: Optional: start date in YYYY-MM-DD format.
|
|
96
|
+
end_date: Optional: end date in YYYY-MM-DD format.
|
|
97
|
+
division_number: Optional: specific division number.
|
|
98
|
+
include_when_member_was_teller: Optional: include when member was a teller.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Lords divisions grouped by party.
|
|
102
|
+
"""
|
|
103
|
+
url = build_url(
|
|
104
|
+
f"{LORDS_VOTES_API_BASE}/Divisions/groupedbyparty",
|
|
105
|
+
{
|
|
106
|
+
"SearchTerm": search_term,
|
|
107
|
+
"MemberId": member_id,
|
|
108
|
+
"StartDate": start_date,
|
|
109
|
+
"EndDate": end_date,
|
|
110
|
+
"DivisionNumber": division_number,
|
|
111
|
+
"IncludeWhenMemberWasTeller": include_when_member_was_teller,
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
return await get_result(url)
|
|
115
|
+
|
|
116
|
+
@mcp.tool()
|
|
117
|
+
async def get_lords_divisions_search_count(
|
|
118
|
+
search_term: str | None = None,
|
|
119
|
+
member_id: int | None = None,
|
|
120
|
+
start_date: str | None = None,
|
|
121
|
+
end_date: str | None = None,
|
|
122
|
+
division_number: int | None = None,
|
|
123
|
+
include_when_member_was_teller: bool | None = None,
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Get total count of House of Lords divisions matching search criteria. Use when you need to know how many Lords divisions match your search parameters before retrieving the actual results.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
search_term: Optional: search term to filter divisions.
|
|
129
|
+
member_id: Optional: member ID to filter divisions.
|
|
130
|
+
start_date: Optional: start date in YYYY-MM-DD format.
|
|
131
|
+
end_date: Optional: end date in YYYY-MM-DD format.
|
|
132
|
+
division_number: Optional: specific division number.
|
|
133
|
+
include_when_member_was_teller: Optional: include when member was a teller.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Total count of matching Lords divisions.
|
|
137
|
+
"""
|
|
138
|
+
url = build_url(
|
|
139
|
+
f"{LORDS_VOTES_API_BASE}/Divisions/searchTotalResults",
|
|
140
|
+
{
|
|
141
|
+
"SearchTerm": search_term,
|
|
142
|
+
"MemberId": member_id,
|
|
143
|
+
"StartDate": start_date,
|
|
144
|
+
"EndDate": end_date,
|
|
145
|
+
"DivisionNumber": division_number,
|
|
146
|
+
"IncludeWhenMemberWasTeller": include_when_member_was_teller,
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
return await get_result(url)
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Members API tools for MPs, Lords, constituencies, and parties."""
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from mcp.server.fastmcp import FastMCP
|
|
5
|
+
|
|
6
|
+
from uk_parliament_mcp.http_client import build_url, get_result
|
|
7
|
+
|
|
8
|
+
MEMBERS_API_BASE = "https://members-api.parliament.uk/api"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_tools(mcp: FastMCP) -> None:
|
|
12
|
+
"""Register member tools with the MCP server."""
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
async def get_member_by_name(name: str) -> str:
|
|
16
|
+
"""Search for MPs and Lords by name with comprehensive member details | find MP, search politician, lookup member, who is, member search, parliamentary representative | Use for identifying members, checking spellings, finding member IDs, or getting basic member information | Returns member profiles with names, parties, constituencies, and current status
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: Full or partial name to search for. Examples: 'Boris Johnson', 'Keir Starmer', 'Smith'. Searches current and former members.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Member profiles with names, parties, constituencies, and current status.
|
|
23
|
+
"""
|
|
24
|
+
url = f"{MEMBERS_API_BASE}/Members/Search?Name={quote(name)}"
|
|
25
|
+
return await get_result(url)
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
async def get_answering_bodies() -> str:
|
|
29
|
+
"""Get government departments and their parliamentary responsibilities | government departments, ministries, answering bodies, policy areas, department structure, who answers questions | Use for understanding government structure, finding responsible departments, or determining who answers questions on specific topics | Returns department names, abbreviations, and policy responsibilities
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Department names, abbreviations, and policy responsibilities.
|
|
33
|
+
"""
|
|
34
|
+
url = f"{MEMBERS_API_BASE}/Reference/AnsweringBodies"
|
|
35
|
+
return await get_result(url)
|
|
36
|
+
|
|
37
|
+
@mcp.tool()
|
|
38
|
+
async def get_member_by_id(member_id: int) -> str:
|
|
39
|
+
"""Get comprehensive member profile by ID with full parliamentary details | member details, MP profile, member information, parliamentary roles, constituency data | Use when you have a member ID and need complete biographical, political, and contact information | Returns detailed member data including roles, constituency, party, and career information
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
member_id: Parliament member ID. Required: get from member search first. Example: 1423
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Detailed member data including roles, constituency, party, and career information.
|
|
46
|
+
"""
|
|
47
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}"
|
|
48
|
+
return await get_result(url)
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
async def edms_for_member_id(member_id: int) -> str:
|
|
52
|
+
"""Get all Early Day Motions signed by a specific MP. Use when you want to see what issues a particular member has supported or their political priorities through EDM signatures.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
member_id: Parliament member ID to get EDMs for.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of Early Day Motions signed by the member.
|
|
59
|
+
"""
|
|
60
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Edms"
|
|
61
|
+
return await get_result(url)
|
|
62
|
+
|
|
63
|
+
@mcp.tool()
|
|
64
|
+
async def parties_list_by_house(house: int) -> str:
|
|
65
|
+
"""Get list of active political parties in either House of Commons (1) or House of Lords (2). Use when you need to know current party representation or party structures in Parliament.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
house: House number: 1 for Commons, 2 for Lords.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of active political parties in the specified house.
|
|
72
|
+
"""
|
|
73
|
+
url = f"{MEMBERS_API_BASE}/Parties/GetActive/{house}"
|
|
74
|
+
return await get_result(url)
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
async def get_departments() -> str:
|
|
78
|
+
"""Get list of all government departments. Use when you need to know the structure of government or which department handles specific policy areas.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of all government departments.
|
|
82
|
+
"""
|
|
83
|
+
url = f"{MEMBERS_API_BASE}/Reference/Departments"
|
|
84
|
+
return await get_result(url)
|
|
85
|
+
|
|
86
|
+
@mcp.tool()
|
|
87
|
+
async def get_contributions(member_id: int) -> str:
|
|
88
|
+
"""Get summary of parliamentary contributions (speeches, questions, interventions) made by a specific member. Use when analyzing an MP or Lord's parliamentary activity and participation levels.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
member_id: Parliament member ID to get contribution summary for.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Summary of parliamentary contributions for the member.
|
|
95
|
+
"""
|
|
96
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/ContributionSummary?page=1"
|
|
97
|
+
return await get_result(url)
|
|
98
|
+
|
|
99
|
+
@mcp.tool()
|
|
100
|
+
async def get_constituencies(
|
|
101
|
+
skip: int | None = None,
|
|
102
|
+
take: int | None = None,
|
|
103
|
+
) -> str:
|
|
104
|
+
"""Get list of UK parliamentary constituencies with pagination support. Use when you need constituency information, want to browse all constituencies, or need constituency data for analysis.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
skip: Number of constituencies to skip (for pagination).
|
|
108
|
+
take: Number of constituencies to return (default 20, max 100).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of UK parliamentary constituencies.
|
|
112
|
+
"""
|
|
113
|
+
url = build_url(
|
|
114
|
+
f"{MEMBERS_API_BASE}/Location/Constituency/Search",
|
|
115
|
+
{"skip": skip, "take": take},
|
|
116
|
+
)
|
|
117
|
+
return await get_result(url)
|
|
118
|
+
|
|
119
|
+
@mcp.tool()
|
|
120
|
+
async def get_election_results_for_constituency(constituency_id: int) -> str:
|
|
121
|
+
"""Get historical election results for a specific constituency. Use when researching constituency voting patterns, election history, or past electoral outcomes for a particular area.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
constituency_id: Unique constituency ID number.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Historical election results for the constituency.
|
|
128
|
+
"""
|
|
129
|
+
url = f"{MEMBERS_API_BASE}/Location/Constituency/{constituency_id}/ElectionResults"
|
|
130
|
+
return await get_result(url)
|
|
131
|
+
|
|
132
|
+
@mcp.tool()
|
|
133
|
+
async def get_lords_interests_staff(search_term: str = "richard") -> str:
|
|
134
|
+
"""Search for staff interests declared by Lords. Use when investigating potential conflicts of interest related to Lords' staff or understanding transparency requirements for parliamentary staff.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
search_term: Search term for staff names or interests (default 'richard').
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Staff interests declared by Lords matching the search term.
|
|
141
|
+
"""
|
|
142
|
+
url = f"{MEMBERS_API_BASE}/LordsInterests/Staff?searchTerm={quote(search_term)}"
|
|
143
|
+
return await get_result(url)
|
|
144
|
+
|
|
145
|
+
@mcp.tool()
|
|
146
|
+
async def get_members_biography(member_id: int) -> str:
|
|
147
|
+
"""Get comprehensive member biography and personal history | MP background, life story, career details, education, personal info, political experience | Use for researching member backgrounds, writing profiles, understanding political journey | Returns detailed biographical data including education, career timeline, and political milestones
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
member_id: Parliament member ID. Required: get from member search first. Returns comprehensive biographical information.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Detailed biographical data including education, career timeline, and political milestones.
|
|
154
|
+
"""
|
|
155
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Biography"
|
|
156
|
+
return await get_result(url)
|
|
157
|
+
|
|
158
|
+
@mcp.tool()
|
|
159
|
+
async def get_members_contact(member_id: int) -> str:
|
|
160
|
+
"""Get member contact details and office information | MP contact, phone number, email, office address, constituency office | Use for contacting members, finding office locations, or getting official contact details | Returns phone numbers, addresses, and official contact information
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
member_id: Parliament member ID. Required: get from member search first. Returns official contact details.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Phone numbers, addresses, and official contact information.
|
|
167
|
+
"""
|
|
168
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Contact"
|
|
169
|
+
return await get_result(url)
|
|
170
|
+
|
|
171
|
+
@mcp.tool()
|
|
172
|
+
async def search_members(
|
|
173
|
+
name: str | None = None,
|
|
174
|
+
location: str | None = None,
|
|
175
|
+
post_title: str | None = None,
|
|
176
|
+
party_id: int | None = None,
|
|
177
|
+
house: int | None = None,
|
|
178
|
+
constituency_id: int | None = None,
|
|
179
|
+
name_starts_with: str | None = None,
|
|
180
|
+
gender: str | None = None,
|
|
181
|
+
membership_started_since: str | None = None,
|
|
182
|
+
membership_ended_since: str | None = None,
|
|
183
|
+
was_member_on_or_after: str | None = None,
|
|
184
|
+
was_member_on_or_before: str | None = None,
|
|
185
|
+
was_member_of_house: int | None = None,
|
|
186
|
+
is_eligible: bool | None = None,
|
|
187
|
+
is_current_member: bool | None = None,
|
|
188
|
+
policy_interest_id: int | None = None,
|
|
189
|
+
experience: str | None = None,
|
|
190
|
+
skip: int = 0,
|
|
191
|
+
take: int = 20,
|
|
192
|
+
) -> str:
|
|
193
|
+
"""Search for current MPs and Lords with comprehensive filtering options. Use when you need to find members by name, location, party, constituency, gender, posts held, or policy interests. Supports advanced search criteria including membership dates and eligibility status.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
name: Optional: full or partial name to search for.
|
|
197
|
+
location: Optional: location or constituency name.
|
|
198
|
+
post_title: Optional: post title (e.g. 'Minister', 'Secretary of State').
|
|
199
|
+
party_id: Optional: party ID to filter by.
|
|
200
|
+
house: Optional: house number (1=Commons, 2=Lords).
|
|
201
|
+
constituency_id: Optional: constituency ID to filter by.
|
|
202
|
+
name_starts_with: Optional: filter names starting with specific letter(s).
|
|
203
|
+
gender: Optional: gender filter ('M' or 'F').
|
|
204
|
+
membership_started_since: Optional: membership started since date in YYYY-MM-DD format.
|
|
205
|
+
membership_ended_since: Optional: membership ended since date in YYYY-MM-DD format.
|
|
206
|
+
was_member_on_or_after: Optional: was member on or after date in YYYY-MM-DD format.
|
|
207
|
+
was_member_on_or_before: Optional: was member on or before date in YYYY-MM-DD format.
|
|
208
|
+
was_member_of_house: Optional: was member of house (1=Commons, 2=Lords).
|
|
209
|
+
is_eligible: Optional: filter by eligibility status.
|
|
210
|
+
is_current_member: Optional: filter by current membership status.
|
|
211
|
+
policy_interest_id: Optional: policy interest ID to filter by.
|
|
212
|
+
experience: Optional: search term for professional experience.
|
|
213
|
+
skip: Number of records to skip (for pagination).
|
|
214
|
+
take: Number of records to return (default 20, max 100).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Matching member profiles with comprehensive details.
|
|
218
|
+
"""
|
|
219
|
+
url = build_url(
|
|
220
|
+
f"{MEMBERS_API_BASE}/Members/Search",
|
|
221
|
+
{
|
|
222
|
+
"Name": name,
|
|
223
|
+
"Location": location,
|
|
224
|
+
"PostTitle": post_title,
|
|
225
|
+
"PartyId": party_id,
|
|
226
|
+
"House": house,
|
|
227
|
+
"ConstituencyId": constituency_id,
|
|
228
|
+
"NameStartsWith": name_starts_with,
|
|
229
|
+
"Gender": gender,
|
|
230
|
+
"MembershipStartedSince": membership_started_since,
|
|
231
|
+
"MembershipEnded.MembershipEndedSince": membership_ended_since,
|
|
232
|
+
"MembershipInDateRange.WasMemberOnOrAfter": was_member_on_or_after,
|
|
233
|
+
"MembershipInDateRange.WasMemberOnOrBefore": was_member_on_or_before,
|
|
234
|
+
"MembershipInDateRange.WasMemberOfHouse": was_member_of_house,
|
|
235
|
+
"IsEligible": is_eligible,
|
|
236
|
+
"IsCurrentMember": is_current_member,
|
|
237
|
+
"PolicyInterestId": policy_interest_id,
|
|
238
|
+
"Experience": experience,
|
|
239
|
+
"skip": skip,
|
|
240
|
+
"take": take,
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
return await get_result(url)
|
|
244
|
+
|
|
245
|
+
@mcp.tool()
|
|
246
|
+
async def search_members_historical(
|
|
247
|
+
name: str | None = None,
|
|
248
|
+
date_to_search_for: str | None = None,
|
|
249
|
+
skip: int = 0,
|
|
250
|
+
take: int = 20,
|
|
251
|
+
) -> str:
|
|
252
|
+
"""Search for historical members who were active on a specific date. Use when you need to find MPs or Lords who served during a particular period in parliamentary history.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
name: Optional: full or partial name to search for.
|
|
256
|
+
date_to_search_for: Optional: specific date to search for members active on that date (YYYY-MM-DD format).
|
|
257
|
+
skip: Number of records to skip (for pagination).
|
|
258
|
+
take: Number of records to return (default 20, max 100).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Historical members matching the search criteria.
|
|
262
|
+
"""
|
|
263
|
+
url = build_url(
|
|
264
|
+
f"{MEMBERS_API_BASE}/Members/SearchHistorical",
|
|
265
|
+
{
|
|
266
|
+
"name": name,
|
|
267
|
+
"dateToSearchFor": date_to_search_for,
|
|
268
|
+
"skip": skip,
|
|
269
|
+
"take": take,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
return await get_result(url)
|
|
273
|
+
|
|
274
|
+
@mcp.tool()
|
|
275
|
+
async def get_member_experience(member_id: int) -> str:
|
|
276
|
+
"""Get professional experience and career background of a member by ID. Use when researching a member's qualifications, previous employment, education, or professional history before entering Parliament.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
member_id: Parliament member ID to get professional experience for.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Professional experience and career background of the member.
|
|
283
|
+
"""
|
|
284
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Experience"
|
|
285
|
+
return await get_result(url)
|
|
286
|
+
|
|
287
|
+
@mcp.tool()
|
|
288
|
+
async def get_member_focus(member_id: int) -> str:
|
|
289
|
+
"""Get areas of focus and policy interests of a member by ID. Use when understanding what issues and policy areas a member prioritizes or specializes in.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
member_id: Parliament member ID to get policy focus areas for.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Areas of focus and policy interests of the member.
|
|
296
|
+
"""
|
|
297
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Focus"
|
|
298
|
+
return await get_result(url)
|
|
299
|
+
|
|
300
|
+
@mcp.tool()
|
|
301
|
+
async def get_member_registered_interests(
|
|
302
|
+
member_id: int,
|
|
303
|
+
house: int | None = None,
|
|
304
|
+
) -> str:
|
|
305
|
+
"""Get registered interests of a member by ID and house. Use when investigating potential conflicts of interest, financial interests, or external roles. Shows declared interests like directorships, consultancies, and gifts.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
member_id: Parliament member ID to get registered interests for.
|
|
309
|
+
house: Optional: house number (1=Commons, 2=Lords).
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Registered interests of the member.
|
|
313
|
+
"""
|
|
314
|
+
url = build_url(
|
|
315
|
+
f"{MEMBERS_API_BASE}/Members/{member_id}/RegisteredInterests",
|
|
316
|
+
{"house": house},
|
|
317
|
+
)
|
|
318
|
+
return await get_result(url)
|
|
319
|
+
|
|
320
|
+
@mcp.tool()
|
|
321
|
+
async def get_member_staff(member_id: int) -> str:
|
|
322
|
+
"""Get staff members working for a specific MP or Lord by member ID. Use when researching parliamentary office staff, researchers, or support personnel.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
member_id: Parliament member ID to get staff details for.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Staff members working for the member.
|
|
329
|
+
"""
|
|
330
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Staff"
|
|
331
|
+
return await get_result(url)
|
|
332
|
+
|
|
333
|
+
@mcp.tool()
|
|
334
|
+
async def get_member_synopsis(member_id: int) -> str:
|
|
335
|
+
"""Get a brief synopsis or summary about a member by ID. Use when you need a concise overview of a member's background, role, or key information.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
member_id: Parliament member ID to get synopsis for.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Brief synopsis or summary about the member.
|
|
342
|
+
"""
|
|
343
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/Synopsis"
|
|
344
|
+
return await get_result(url)
|
|
345
|
+
|
|
346
|
+
@mcp.tool()
|
|
347
|
+
async def get_member_voting(
|
|
348
|
+
member_id: int,
|
|
349
|
+
house: int,
|
|
350
|
+
page: int | None = None,
|
|
351
|
+
) -> str:
|
|
352
|
+
"""Get voting records of a member by ID for a specific house. Use when analyzing how a member votes, their voting patterns, or their stance on particular issues through their voting history.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
member_id: Parliament member ID to get voting record for.
|
|
356
|
+
house: House number (1=Commons, 2=Lords).
|
|
357
|
+
page: Optional: page number for pagination.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Voting records of the member.
|
|
361
|
+
"""
|
|
362
|
+
url = build_url(
|
|
363
|
+
f"{MEMBERS_API_BASE}/Members/{member_id}/Voting",
|
|
364
|
+
{"house": house, "page": page},
|
|
365
|
+
)
|
|
366
|
+
return await get_result(url)
|
|
367
|
+
|
|
368
|
+
@mcp.tool()
|
|
369
|
+
async def get_member_written_questions(
|
|
370
|
+
member_id: int,
|
|
371
|
+
page: int | None = None,
|
|
372
|
+
) -> str:
|
|
373
|
+
"""Get written questions submitted by a member by ID. Use when researching what questions a member has asked of government departments or their areas of parliamentary inquiry.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
member_id: Parliament member ID to get written questions for.
|
|
377
|
+
page: Optional: page number for pagination.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Written questions submitted by the member.
|
|
381
|
+
"""
|
|
382
|
+
url = build_url(
|
|
383
|
+
f"{MEMBERS_API_BASE}/Members/{member_id}/WrittenQuestions",
|
|
384
|
+
{"page": page},
|
|
385
|
+
)
|
|
386
|
+
return await get_result(url)
|
|
387
|
+
|
|
388
|
+
@mcp.tool()
|
|
389
|
+
async def get_members_history(member_ids: list[int]) -> str:
|
|
390
|
+
"""Get historical information for multiple members by their IDs. Returns name history, party affiliations, and membership details over time. Use when researching how members' details have changed throughout their careers.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
member_ids: List of Parliament member IDs to get history for.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Historical information for the specified members.
|
|
397
|
+
"""
|
|
398
|
+
ids_str = ",".join(str(id) for id in member_ids)
|
|
399
|
+
url = build_url(f"{MEMBERS_API_BASE}/Members/History", {"ids": ids_str})
|
|
400
|
+
return await get_result(url)
|
|
401
|
+
|
|
402
|
+
@mcp.tool()
|
|
403
|
+
async def get_member_latest_election_result(member_id: int) -> str:
|
|
404
|
+
"""Get the latest election result for a member by ID. Use when researching how a member was elected, their constituency performance, vote share, or election margin.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
member_id: Parliament member ID to get latest election result for.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Latest election result for the member.
|
|
411
|
+
"""
|
|
412
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/LatestElectionResult"
|
|
413
|
+
return await get_result(url)
|
|
414
|
+
|
|
415
|
+
@mcp.tool()
|
|
416
|
+
async def get_member_portrait_url(member_id: int) -> str:
|
|
417
|
+
"""Get the portrait image URL for a member by ID. Use when you need a link to a member's official parliamentary portrait photograph.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
member_id: Parliament member ID to get portrait URL for.
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Portrait image URL for the member.
|
|
424
|
+
"""
|
|
425
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/PortraitUrl"
|
|
426
|
+
return await get_result(url)
|
|
427
|
+
|
|
428
|
+
@mcp.tool()
|
|
429
|
+
async def get_member_thumbnail_url(member_id: int) -> str:
|
|
430
|
+
"""Get the thumbnail image URL for a member by ID. Use when you need a link to a smaller version of a member's parliamentary photograph for lists or compact displays.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
member_id: Parliament member ID to get thumbnail URL for.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Thumbnail image URL for the member.
|
|
437
|
+
"""
|
|
438
|
+
url = f"{MEMBERS_API_BASE}/Members/{member_id}/ThumbnailUrl"
|
|
439
|
+
return await get_result(url)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Now API tools for live chamber activity."""
|
|
2
|
+
from mcp.server.fastmcp import FastMCP
|
|
3
|
+
|
|
4
|
+
from uk_parliament_mcp.http_client import get_result
|
|
5
|
+
|
|
6
|
+
NOW_API_BASE = "https://now-api.parliament.uk/api"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_tools(mcp: FastMCP) -> None:
|
|
10
|
+
"""Register now tools with the MCP server."""
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def happening_now_in_commons() -> str:
|
|
14
|
+
"""Get live information about what's currently happening in the House of Commons chamber. Use when you want real-time updates on parliamentary business, current debates, or voting activity.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Live information about current Commons chamber activity.
|
|
18
|
+
"""
|
|
19
|
+
url = f"{NOW_API_BASE}/Message/message/CommonsMain/current"
|
|
20
|
+
return await get_result(url)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
async def happening_now_in_lords() -> str:
|
|
24
|
+
"""Get live information about what's currently happening in the House of Lords chamber. Use when you want real-time updates on Lords business, current debates, or voting activity.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Live information about current Lords chamber activity.
|
|
28
|
+
"""
|
|
29
|
+
url = f"{NOW_API_BASE}/Message/message/LordsMain/current"
|
|
30
|
+
return await get_result(url)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Oral Questions API tools for EDMs and question times."""
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from mcp.server.fastmcp import FastMCP
|
|
5
|
+
|
|
6
|
+
from uk_parliament_mcp.http_client import get_result
|
|
7
|
+
|
|
8
|
+
ORAL_QUESTIONS_API_BASE = "https://oralquestionsandmotions-api.parliament.uk"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_tools(mcp: FastMCP) -> None:
|
|
12
|
+
"""Register oral questions tools with the MCP server."""
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
async def get_recently_tabled_edms(take: int = 10) -> str:
|
|
16
|
+
"""Get recently tabled Early Day Motions - formal political statements | EDMs, political motions, backbench initiatives, MP opinions, parliamentary statements, political positions, cross-party support | Use for tracking political sentiment, finding MP stances on issues, monitoring backbench activity, or researching political movements | Returns recent EDMs with titles, sponsors, supporters, and tabling dates | Data freshness: updated daily
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
take: Number of EDMs to return. Default: 10, recommended max: 50. Recent motions returned first.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Recent EDMs with titles, sponsors, supporters, and tabling dates.
|
|
23
|
+
"""
|
|
24
|
+
url = f"{ORAL_QUESTIONS_API_BASE}/EarlyDayMotions/list?parameters.orderBy=DateTabledDesc&skip=0&take={take}"
|
|
25
|
+
return await get_result(url)
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
async def search_early_day_motions(search_term: str) -> str:
|
|
29
|
+
"""Search Early Day Motions by topic or keyword. Use when researching MP opinions on specific issues or finding motions related to particular subjects. EDMs often reflect backbench MP concerns.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
search_term: Search term for EDM topics or content (e.g. 'climate change', 'NHS funding').
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
EDMs matching the search term.
|
|
36
|
+
"""
|
|
37
|
+
url = f"{ORAL_QUESTIONS_API_BASE}/EarlyDayMotions/list?parameters.searchTerm={quote(search_term)}"
|
|
38
|
+
return await get_result(url)
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
async def search_oral_question_times(
|
|
42
|
+
answering_date_start: str,
|
|
43
|
+
answering_date_end: str,
|
|
44
|
+
) -> str:
|
|
45
|
+
"""Get scheduled oral question times for ministers in Parliament. Use when you want to know when specific departments will answer questions or when particular topics will be discussed.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
answering_date_start: Start date for question times in YYYY-MM-DD format.
|
|
49
|
+
answering_date_end: End date for question times in YYYY-MM-DD format.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Scheduled oral question times in the date range.
|
|
53
|
+
"""
|
|
54
|
+
url = f"{ORAL_QUESTIONS_API_BASE}/oralquestiontimes/list?parameters.answeringDateStart={quote(answering_date_start)}¶meters.answeringDateEnd={quote(answering_date_end)}"
|
|
55
|
+
return await get_result(url)
|