@mountainpass/addressr-mcp 0.1.0 → 0.2.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/package.json +4 -2
- package/src/server.mjs +61 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mountainpass/addressr-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Australian address search and validation via Addressr",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mountain Pass",
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
"test": "node --test test/server.test.mjs",
|
|
23
23
|
"lint": "eslint .",
|
|
24
24
|
"pre-commit": "lint-staged",
|
|
25
|
-
"push:watch": "bash scripts/push-and-watch.sh"
|
|
25
|
+
"push:watch": "bash scripts/push-and-watch.sh",
|
|
26
|
+
"release:watch": "bash scripts/release-watch.sh"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"@windyroad/fetch-link": "^3.1.0",
|
|
29
31
|
"zod": "^4.3.0"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
package/src/server.mjs
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { glowUpFetchWithLinks } from '@windyroad/fetch-link';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
|
|
6
|
+
const API_URL =
|
|
7
|
+
process.env.ADDRESSR_API_URL || 'https://addressr.p.rapidapi.com/';
|
|
5
8
|
const API_HOST = process.env.RAPIDAPI_HOST || 'addressr.p.rapidapi.com';
|
|
6
|
-
const API_BASE = `https://${API_HOST}`;
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
headers: {
|
|
11
|
-
'x-rapidapi-key': key,
|
|
12
|
-
'x-rapidapi-host': API_HOST,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
17
|
-
}
|
|
18
|
-
return response.json();
|
|
19
|
-
}
|
|
10
|
+
const SEARCH_REL = 'https://addressr.io/rels/address-search';
|
|
11
|
+
const HEALTH_REL = 'https://addressr.io/rels/health';
|
|
20
12
|
|
|
21
13
|
export function createServer() {
|
|
22
14
|
const key = process.env.RAPIDAPI_KEY;
|
|
@@ -27,6 +19,28 @@ export function createServer() {
|
|
|
27
19
|
process.exit(1);
|
|
28
20
|
}
|
|
29
21
|
|
|
22
|
+
const headers = {
|
|
23
|
+
'x-rapidapi-key': key,
|
|
24
|
+
'x-rapidapi-host': API_HOST,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Create a fetch-link instance with RapidAPI auth headers baked in
|
|
28
|
+
const fetchLink = glowUpFetchWithLinks((url, init) =>
|
|
29
|
+
fetch(url, { ...init, headers: { ...headers, ...init?.headers } }),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Cache the API root discovery (1 week cache-control)
|
|
33
|
+
let rootPromise;
|
|
34
|
+
function getRoot() {
|
|
35
|
+
if (!rootPromise) {
|
|
36
|
+
rootPromise = fetchLink(API_URL).catch((err) => {
|
|
37
|
+
rootPromise = undefined;
|
|
38
|
+
throw err;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return rootPromise;
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
const server = new McpServer({
|
|
31
45
|
name: 'addressr',
|
|
32
46
|
version: '0.1.0',
|
|
@@ -47,9 +61,15 @@ export function createServer() {
|
|
|
47
61
|
.describe('Page number for paginated results (default: first page)'),
|
|
48
62
|
},
|
|
49
63
|
async ({ q, page }) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
const root = await getRoot();
|
|
65
|
+
const params = { q };
|
|
66
|
+
if (page !== undefined) params.page = String(page);
|
|
67
|
+
const searchLinks = root.links(SEARCH_REL, params);
|
|
68
|
+
if (!searchLinks.length) {
|
|
69
|
+
throw new Error('Search link relation not found in API root');
|
|
70
|
+
}
|
|
71
|
+
const response = await fetchLink(searchLinks[0]);
|
|
72
|
+
const data = await response.json();
|
|
53
73
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
54
74
|
},
|
|
55
75
|
);
|
|
@@ -65,7 +85,17 @@ export function createServer() {
|
|
|
65
85
|
),
|
|
66
86
|
},
|
|
67
87
|
async ({ addressId }) => {
|
|
68
|
-
|
|
88
|
+
// Follow the canonical link pattern from search results
|
|
89
|
+
// The search response includes canonical links: </addresses/{pid}>; rel=canonical
|
|
90
|
+
// We construct the same URL and follow it via fetchLink
|
|
91
|
+
const root = await getRoot();
|
|
92
|
+
const baseUrl = new URL(root.url);
|
|
93
|
+
const addressUrl = new URL(
|
|
94
|
+
`/addresses/${encodeURIComponent(addressId)}`,
|
|
95
|
+
baseUrl,
|
|
96
|
+
);
|
|
97
|
+
const response = await fetchLink(addressUrl.toString());
|
|
98
|
+
const data = await response.json();
|
|
69
99
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
70
100
|
},
|
|
71
101
|
);
|
|
@@ -75,7 +105,20 @@ export function createServer() {
|
|
|
75
105
|
'Check API service status. Returns version, timestamp, and health status.',
|
|
76
106
|
{},
|
|
77
107
|
async () => {
|
|
78
|
-
const
|
|
108
|
+
const root = await getRoot();
|
|
109
|
+
const healthLinks = root.links(HEALTH_REL);
|
|
110
|
+
if (healthLinks.length && healthLinks[0].uri !== 'undefined') {
|
|
111
|
+
const response = await fetchLink(healthLinks[0]);
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Fallback: health link URI is currently broken in production (returns "undefined")
|
|
118
|
+
const baseUrl = new URL(root.url);
|
|
119
|
+
const healthUrl = new URL('/health', baseUrl);
|
|
120
|
+
const response = await fetchLink(healthUrl.toString());
|
|
121
|
+
const data = await response.json();
|
|
79
122
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
80
123
|
},
|
|
81
124
|
);
|