@pipeworx/mcp-landregistry-uk 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +166 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# mcp-landregistry-uk
|
|
2
|
+
|
|
3
|
+
HM Land Registry Price Paid Data (UK) MCP.
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 693+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"landregistry-uk": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/landregistry-uk/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 693+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Landregistry Uk data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-landregistry-uk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HM Land Registry Price Paid Data (UK) MCP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "landregistry-uk"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-landregistry-uk"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/landregistry-uk",
|
|
4
|
+
"title": "Landregistry Uk",
|
|
5
|
+
"description": "HM Land Registry Price Paid Data (UK) MCP.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/landregistry-uk",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-landregistry-uk",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/landregistry-uk/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* HM Land Registry Price Paid Data (UK) MCP.
|
|
21
|
+
*
|
|
22
|
+
* England & Wales residential property *sale prices* recorded by HM Land Registry,
|
|
23
|
+
* covering every reported sale since 1995. Served by the keyless Linked Data API
|
|
24
|
+
* (Elda / linked-data-api) at landregistry.data.gov.uk — no API key required.
|
|
25
|
+
*
|
|
26
|
+
* Data scope & conventions:
|
|
27
|
+
* - Geography: England and Wales only (Scotland and Northern Ireland keep separate registries).
|
|
28
|
+
* - Postcodes are UK-format, UPPERCASE, with a space, e.g. "SW1A 1AA", "WA2 8SN".
|
|
29
|
+
* - pricePaid is in whole GBP (£). transactionDate is the date of sale (YYYY-MM-DD).
|
|
30
|
+
* - Each record carries propertyType (detached/semi-detached/terraced/flat-maisonette/other),
|
|
31
|
+
* estateType (Freehold/Leasehold — i.e. tenure), and newBuild (boolean).
|
|
32
|
+
*
|
|
33
|
+
* Elda filter / range / sort syntax (verified against the live API):
|
|
34
|
+
* - Exact match: field=value e.g. propertyAddress.town=WARRINGTON
|
|
35
|
+
* - Nested fields: dot notation e.g. propertyAddress.postcode=WA2 8SN
|
|
36
|
+
* - Range (NOTE the PREFIX form, not a suffix):
|
|
37
|
+
* min-field=value / max-field=value e.g. min-pricePaid=400000, max-transactionDate=2020-12-31
|
|
38
|
+
* (The suffix form `field-min` returns HTTP 400 "unrecognised parameter prefix".)
|
|
39
|
+
* - Sort: _sort=field (ascending) / _sort=-field (descending)
|
|
40
|
+
* - Paging: _pageSize=N (max ~500) & _page=P (0-indexed)
|
|
41
|
+
* Responses are { result: { items: [...], page, itemsPerPage, startIndex, next, ... } }.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const BASE = 'https://landregistry.data.gov.uk/data/ppi';
|
|
46
|
+
const UA = 'pipeworx-mcp-landregistry-uk/1.0 (+https://pipeworx.io)';
|
|
47
|
+
|
|
48
|
+
const tools: McpToolExport['tools'] = [
|
|
49
|
+
{
|
|
50
|
+
name: 'search_transactions',
|
|
51
|
+
description:
|
|
52
|
+
'Search HM Land Registry Price Paid records — individual residential property sales in England & Wales since 1995. ' +
|
|
53
|
+
'Filter by postcode, town, district, county, price range and sale-date range; sort and page through results. ' +
|
|
54
|
+
'Each record returns price paid (£GBP), sale date, full address, property type, tenure (freehold/leasehold) and new-build flag. ' +
|
|
55
|
+
'Postcodes must be UPPERCASE with a space (e.g. "SW1A 1AA").',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
postcode: { type: 'string', description: 'UK postcode, UPPERCASE with a space, e.g. "WA2 8SN". Exact match.' },
|
|
60
|
+
town: { type: 'string', description: 'Post town, exact match (case-insensitive in practice), e.g. "WARRINGTON".' },
|
|
61
|
+
district: { type: 'string', description: 'Local district, exact match, e.g. "MANCHESTER".' },
|
|
62
|
+
county: { type: 'string', description: 'County, exact match, e.g. "GREATER MANCHESTER".' },
|
|
63
|
+
minPrice: { type: 'number', description: 'Minimum price paid in whole GBP (inclusive).' },
|
|
64
|
+
maxPrice: { type: 'number', description: 'Maximum price paid in whole GBP (inclusive).' },
|
|
65
|
+
minDate: { type: 'string', description: 'Earliest sale date, YYYY-MM-DD (inclusive). Data starts 1995.' },
|
|
66
|
+
maxDate: { type: 'string', description: 'Latest sale date, YYYY-MM-DD (inclusive).' },
|
|
67
|
+
date: { type: 'string', description: 'Exact sale date, YYYY-MM-DD. Overrides minDate/maxDate.' },
|
|
68
|
+
sort: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description:
|
|
71
|
+
'Sort field. Prefix with "-" for descending. Common: "-transactionDate" (newest first), "transactionDate", "-pricePaid", "pricePaid". Default newest first.',
|
|
72
|
+
},
|
|
73
|
+
pageSize: { type: 'number', description: 'Results per page, 1-500. Default 25.' },
|
|
74
|
+
page: { type: 'number', description: '0-indexed page number. Default 0.' },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'lookup_postcode',
|
|
80
|
+
description:
|
|
81
|
+
'Convenience: list the most recent HM Land Registry property sales for a single UK postcode (England & Wales). ' +
|
|
82
|
+
'Returns sale price (£GBP), date, address, property type, tenure and new-build flag, newest sale first. ' +
|
|
83
|
+
'Postcode must be UPPERCASE with a space, e.g. "SW1A 1AA".',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
postcode: { type: 'string', description: 'UK postcode, UPPERCASE with a space, e.g. "WA2 8SN".' },
|
|
88
|
+
limit: { type: 'number', description: 'Max records to return, 1-500. Default 25.' },
|
|
89
|
+
},
|
|
90
|
+
required: ['postcode'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
96
|
+
switch (name) {
|
|
97
|
+
case 'search_transactions': {
|
|
98
|
+
const q = new URLSearchParams();
|
|
99
|
+
q.set('_pageSize', String(clampPage(args.pageSize, 25)));
|
|
100
|
+
q.set('_page', String(Math.max(0, intArg(args.page, 0))));
|
|
101
|
+
q.set('_sort', strArg(args.sort) ?? '-transactionDate');
|
|
102
|
+
|
|
103
|
+
const postcode = strArg(args.postcode);
|
|
104
|
+
if (postcode) q.set('propertyAddress.postcode', postcode);
|
|
105
|
+
const town = strArg(args.town);
|
|
106
|
+
if (town) q.set('propertyAddress.town', town);
|
|
107
|
+
const district = strArg(args.district);
|
|
108
|
+
if (district) q.set('propertyAddress.district', district);
|
|
109
|
+
const county = strArg(args.county);
|
|
110
|
+
if (county) q.set('propertyAddress.county', county);
|
|
111
|
+
|
|
112
|
+
if (typeof args.minPrice === 'number') q.set('min-pricePaid', String(args.minPrice));
|
|
113
|
+
if (typeof args.maxPrice === 'number') q.set('max-pricePaid', String(args.maxPrice));
|
|
114
|
+
|
|
115
|
+
const date = strArg(args.date);
|
|
116
|
+
if (date) {
|
|
117
|
+
q.set('transactionDate', date);
|
|
118
|
+
} else {
|
|
119
|
+
const minDate = strArg(args.minDate);
|
|
120
|
+
if (minDate) q.set('min-transactionDate', minDate);
|
|
121
|
+
const maxDate = strArg(args.maxDate);
|
|
122
|
+
if (maxDate) q.set('max-transactionDate', maxDate);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return ppiGet(`/transaction-record.json?${q.toString()}`);
|
|
126
|
+
}
|
|
127
|
+
case 'lookup_postcode': {
|
|
128
|
+
const postcode = reqStr(args, 'postcode', '"SW1A 1AA"');
|
|
129
|
+
const q = new URLSearchParams();
|
|
130
|
+
q.set('_pageSize', String(clampPage(args.limit, 25)));
|
|
131
|
+
q.set('_page', '0');
|
|
132
|
+
q.set('_sort', '-transactionDate');
|
|
133
|
+
q.set('propertyAddress.postcode', postcode);
|
|
134
|
+
return ppiGet(`/transaction-record.json?${q.toString()}`);
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function ppiGet(path: string): Promise<unknown> {
|
|
142
|
+
const res = await fetch(`${BASE}${path}`, { headers: { Accept: 'application/json', 'User-Agent': UA } });
|
|
143
|
+
if (!res.ok) throw new Error(`Land Registry: ${res.status} ${await res.text().then((t) => t.slice(0, 200))}`);
|
|
144
|
+
return res.json();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function strArg(v: unknown): string | undefined {
|
|
148
|
+
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function intArg(v: unknown, fallback: number): number {
|
|
152
|
+
return typeof v === 'number' && Number.isFinite(v) ? Math.trunc(v) : fallback;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function clampPage(v: unknown, fallback: number): number {
|
|
156
|
+
const n = intArg(v, fallback);
|
|
157
|
+
return Math.min(500, Math.max(1, n));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function reqStr(args: Record<string, unknown>, key: string, example: string): string {
|
|
161
|
+
const v = args[key];
|
|
162
|
+
if (typeof v !== 'string' || !v.trim()) throw new Error(`Required argument "${key}" is missing. Pass a string like ${example}.`);
|
|
163
|
+
return v.trim();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|