campertunity-mcp-server 0.0.7 → 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/README.md +31 -19
- package/dist/campertunity/client.js +9 -7
- package/dist/index.js +8 -8
- package/dist/tools/{place_availability.js → listing_availability.js} +5 -6
- package/dist/tools/{place_book.js → listing_book.js} +6 -7
- package/dist/tools/{place_details.js → listing_details.js} +6 -7
- package/dist/tools/{place_search.js → listing_search.js} +22 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,21 @@ This server implements the Model Context Protocol (MCP) for Campertunity, provid
|
|
|
6
6
|
|
|
7
7
|
## MCP Client Config
|
|
8
8
|
|
|
9
|
+
```
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"campground-search-mcp-server": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "campertunity-mcp-server@latest"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
No API key is required to get started. To get higher rate limits, get an API key from [https://campertunity.com/mcp](https://campertunity.com/mcp) and set it as an environment variable:
|
|
23
|
+
|
|
9
24
|
```
|
|
10
25
|
{
|
|
11
26
|
"mcpServers": {
|
|
@@ -20,20 +35,13 @@ This server implements the Model Context Protocol (MCP) for Campertunity, provid
|
|
|
20
35
|
}
|
|
21
36
|
```
|
|
22
37
|
|
|
23
|
-
## Setup
|
|
24
|
-
|
|
25
|
-
1. Get your API key from [https://campertunity.com/mcp](https://campertunity.com/mcp)
|
|
26
|
-
2. Set the environment variable:
|
|
27
|
-
```
|
|
28
|
-
CAMPERTUNITY_API_KEY=your_api_key_here
|
|
29
|
-
```
|
|
30
|
-
|
|
31
38
|
## Available Tools
|
|
32
39
|
|
|
33
|
-
###
|
|
34
|
-
Search for camping
|
|
40
|
+
### listing-search
|
|
41
|
+
Search for camping listings with various filters and criteria, or browse all listings with cursor pagination.
|
|
35
42
|
- **Parameters:**
|
|
36
43
|
- `limit`: Number of results (default: 50, max: 1000)
|
|
44
|
+
- `cursor`: Pagination cursor from a previous response (for browsing without search params)
|
|
37
45
|
- `startDate`: Start date for availability (YYYY-MM-DD)
|
|
38
46
|
- `endDate`: End date for availability (YYYY-MM-DD)
|
|
39
47
|
- `adults`: Number of adults (default: 1)
|
|
@@ -41,26 +49,30 @@ Search for camping places with various filters and criteria.
|
|
|
41
49
|
- `latitude`: Center point latitude
|
|
42
50
|
- `longitude`: Center point longitude
|
|
43
51
|
- `radius`: Search radius in kilometers (default: 20)
|
|
52
|
+
- `region`: Region/state to search in (geocoded if lat/lng not provided)
|
|
53
|
+
- `city`: City to search in (geocoded if lat/lng not provided)
|
|
54
|
+
- `country`: Country to search in (geocoded if lat/lng not provided)
|
|
55
|
+
- `countryCode`: Country code to search in, e.g. "US", "CA" (geocoded if lat/lng not provided)
|
|
44
56
|
- `filters`: Array of tags to filter by (see Tag enum below)
|
|
45
57
|
- `campgroundDescription`: Natural language description of desired campground features
|
|
46
58
|
|
|
47
|
-
###
|
|
48
|
-
Get detailed information about a specific
|
|
59
|
+
### listing-details
|
|
60
|
+
Get detailed information about a specific listing.
|
|
49
61
|
- **Parameters:**
|
|
50
|
-
- `
|
|
62
|
+
- `listingId`: ID of the listing to get details for
|
|
51
63
|
|
|
52
|
-
###
|
|
53
|
-
Check availability of camping sites at a specific
|
|
64
|
+
### listing-availability
|
|
65
|
+
Check availability of camping sites at a specific listing.
|
|
54
66
|
- **Parameters:**
|
|
55
|
-
- `
|
|
67
|
+
- `listingId`: ID of the listing to check
|
|
56
68
|
- `siteIds`: Optional array of specific site IDs to check
|
|
57
69
|
- `startDate`: Start date (YYYY-MM-DD)
|
|
58
70
|
- `endDate`: End date (YYYY-MM-DD)
|
|
59
71
|
|
|
60
|
-
###
|
|
61
|
-
|
|
72
|
+
### listing-book
|
|
73
|
+
Get a booking URL for a listing.
|
|
62
74
|
- **Parameters:**
|
|
63
|
-
- `
|
|
75
|
+
- `listingId`: ID of the listing to book
|
|
64
76
|
- `startDate`: Start date (YYYY-MM-DD)
|
|
65
77
|
- `endDate`: End date (YYYY-MM-DD)
|
|
66
78
|
- `adults`: Number of adults (default: 1)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const CAMPERTUNITY_API_URL = process.env.CAMPERTUNITY_API_URL || "https://campertunity.com/public/api";
|
|
2
2
|
const CAMPERTUNITY_API_KEY = process.env.CAMPERTUNITY_API_KEY;
|
|
3
|
-
if (!CAMPERTUNITY_API_KEY) {
|
|
4
|
-
throw new Error("CAMPERTUNITY_API_KEY environment variable is required");
|
|
5
|
-
}
|
|
6
3
|
export class CampertunityClient {
|
|
4
|
+
get headers() {
|
|
5
|
+
const h = {};
|
|
6
|
+
if (CAMPERTUNITY_API_KEY) {
|
|
7
|
+
h["Authorization"] = `Bearer ${CAMPERTUNITY_API_KEY}`;
|
|
8
|
+
}
|
|
9
|
+
return h;
|
|
10
|
+
}
|
|
7
11
|
async get(path) {
|
|
8
12
|
const response = await fetch(`${CAMPERTUNITY_API_URL}${path}`, {
|
|
9
13
|
method: "GET",
|
|
10
|
-
headers:
|
|
11
|
-
Authorization: `Bearer ${CAMPERTUNITY_API_KEY}`,
|
|
12
|
-
},
|
|
14
|
+
headers: this.headers,
|
|
13
15
|
});
|
|
14
16
|
if (!response.ok) {
|
|
15
17
|
throw new Error(`Campertunity API error: ${response.statusText}`);
|
|
@@ -20,7 +22,7 @@ export class CampertunityClient {
|
|
|
20
22
|
const response = await fetch(`${CAMPERTUNITY_API_URL}${path}`, {
|
|
21
23
|
method: "POST",
|
|
22
24
|
headers: {
|
|
23
|
-
|
|
25
|
+
...this.headers,
|
|
24
26
|
"Content-Type": "application/json",
|
|
25
27
|
},
|
|
26
28
|
body: JSON.stringify(data),
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { CampertunityClient } from "./campertunity/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { listingAvailabilityTool } from "./tools/listing_availability.js";
|
|
6
|
+
import { listingBookTool } from "./tools/listing_book.js";
|
|
7
|
+
import { listingDetailsTool } from "./tools/listing_details.js";
|
|
8
|
+
import { listingSearchTool } from "./tools/listing_search.js";
|
|
9
9
|
const campertunityClient = new CampertunityClient();
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: "campertunity-model-context-protocol-server",
|
|
@@ -15,10 +15,10 @@ const server = new McpServer({
|
|
|
15
15
|
tools: {},
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
listingAvailabilityTool(server, campertunityClient);
|
|
19
|
+
listingBookTool(server, campertunityClient);
|
|
20
|
+
listingDetailsTool(server, campertunityClient);
|
|
21
|
+
listingSearchTool(server, campertunityClient);
|
|
22
22
|
async function runServer() {
|
|
23
23
|
const transport = new StdioServerTransport();
|
|
24
24
|
await server.connect(transport);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export const
|
|
3
|
-
server.tool('
|
|
4
|
-
|
|
2
|
+
export const listingAvailabilityTool = (server, campertunityClient) => {
|
|
3
|
+
server.tool('listing-availability', 'Check availability for specific campsites at a listing', {
|
|
4
|
+
listingId: z.string().describe('The id of the listing to check availability for.'),
|
|
5
5
|
siteIds: z.array(z.string()).optional().describe('The ids of the sites to check availability for.'),
|
|
6
6
|
startDate: z.string().describe('The start date to check availability for. Format: YYYY-MM-DD'),
|
|
7
7
|
endDate: z.string().describe('The end date to check availability for. Format: YYYY-MM-DD'),
|
|
8
|
-
}, async ({
|
|
8
|
+
}, async ({ listingId, siteIds, startDate, endDate }) => {
|
|
9
9
|
try {
|
|
10
|
-
const availability = await campertunityClient.post(`/
|
|
11
|
-
placeId,
|
|
10
|
+
const availability = await campertunityClient.post(`/listings/campgrounds/${encodeURIComponent(listingId)}/availability`, {
|
|
12
11
|
siteIds,
|
|
13
12
|
startDate,
|
|
14
13
|
endDate,
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export const
|
|
3
|
-
server.tool('
|
|
4
|
-
|
|
2
|
+
export const listingBookTool = (server, campertunityClient) => {
|
|
3
|
+
server.tool('listing-book', 'Get a booking URL for a campground or recreation site', {
|
|
4
|
+
listingId: z.string().describe('The id of the listing to book.'),
|
|
5
5
|
startDate: z.string().optional().describe('The start date of the booking. Format: YYYY-MM-DD'),
|
|
6
6
|
endDate: z.string().optional().describe('The end date of the booking. Format: YYYY-MM-DD'),
|
|
7
7
|
adults: z.number().default(1).describe('Number of adults. Default is 1.'),
|
|
8
8
|
children: z.number().default(0).describe('Number of children. Default is 0.'),
|
|
9
|
-
}, async ({
|
|
9
|
+
}, async ({ listingId, startDate, endDate, adults, children }) => {
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
12
|
-
placeId,
|
|
11
|
+
const result = await campertunityClient.post(`/listings/campgrounds/${encodeURIComponent(listingId)}/book`, {
|
|
13
12
|
startDate,
|
|
14
13
|
endDate,
|
|
15
14
|
adults,
|
|
16
15
|
children,
|
|
17
16
|
});
|
|
18
17
|
return {
|
|
19
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
18
|
+
content: [{ type: 'text', text: JSON.stringify(result), mimeType: 'application/json' }],
|
|
20
19
|
};
|
|
21
20
|
}
|
|
22
21
|
catch (error) {
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}, async ({ placeId }) => {
|
|
2
|
+
export const listingDetailsTool = (server, campertunityClient) => {
|
|
3
|
+
server.tool('listing-details', 'Get detailed information about a specific listing', {
|
|
4
|
+
listingId: z.string().describe('The id of the listing to get details for.'),
|
|
5
|
+
}, async ({ listingId }) => {
|
|
7
6
|
try {
|
|
8
|
-
const
|
|
7
|
+
const listing = await campertunityClient.get(`/listings/campgrounds/${encodeURIComponent(listingId)}`);
|
|
9
8
|
return {
|
|
10
9
|
content: [
|
|
11
10
|
{
|
|
12
11
|
type: 'text',
|
|
13
|
-
text: JSON.stringify(
|
|
12
|
+
text: JSON.stringify(listing),
|
|
14
13
|
mimeType: 'application/json',
|
|
15
14
|
},
|
|
16
15
|
],
|
|
@@ -49,9 +49,10 @@ export var Tag;
|
|
|
49
49
|
Tag["waterfall"] = "waterfall";
|
|
50
50
|
Tag["creek"] = "creek";
|
|
51
51
|
})(Tag || (Tag = {}));
|
|
52
|
-
export const
|
|
53
|
-
server.tool('
|
|
54
|
-
limit: z.number().default(50).optional().describe('Number of
|
|
52
|
+
export const listingSearchTool = (server, campertunityClient) => {
|
|
53
|
+
server.tool('listing-search', 'Search for campgrounds and outdoor recreation listings', {
|
|
54
|
+
limit: z.number().default(50).optional().describe('Number of listings to return. Default is 50, max is 1000.'),
|
|
55
|
+
cursor: z.string().optional().describe('Pagination cursor from a previous response. Used for browsing all listings without search params.'),
|
|
55
56
|
startDate: z.string().optional().describe('Start date for availability search. Format: YYYY-MM-DD'),
|
|
56
57
|
endDate: z.string().optional().describe('End date for availability search. Format: YYYY-MM-DD'),
|
|
57
58
|
adults: z.number().optional().describe('Number of adults. Default is 1.'),
|
|
@@ -59,13 +60,19 @@ export const placeSearchTool = (server, campertunityClient) => {
|
|
|
59
60
|
latitude: z.number().optional().describe('Latitude to filter by.'),
|
|
60
61
|
longitude: z.number().optional().describe('Longitude to filter by.'),
|
|
61
62
|
radius: z.number().optional().default(20).describe('Radius to filter by (in km).'),
|
|
62
|
-
|
|
63
|
+
region: z.string().optional().describe('Region/state to search in. Will be geocoded to coordinates if latitude/longitude not provided.'),
|
|
64
|
+
city: z.string().optional().describe('City to search in. Will be geocoded to coordinates if latitude/longitude not provided.'),
|
|
65
|
+
country: z.string().optional().describe('Country to search in. Will be geocoded to coordinates if latitude/longitude not provided.'),
|
|
66
|
+
countryCode: z.string().optional().describe('Country code to search in (e.g. "US", "CA"). Will be geocoded to coordinates if latitude/longitude not provided.'),
|
|
67
|
+
filters: z.array(z.enum(Object.values(Tag))).optional().describe('Filter out listings that have specific tags.'),
|
|
63
68
|
campgroundDescription: z.string().optional().describe('Describe the campground you are looking for. Note: not the location, but something about the campground like "has a pool" or "near a lake" or "has a playground"'),
|
|
64
|
-
}, async ({ limit, startDate, endDate, adults, children, latitude, longitude, radius, filters, campgroundDescription }) => {
|
|
69
|
+
}, async ({ limit, cursor, startDate, endDate, adults, children, latitude, longitude, radius, region, city, country, countryCode, filters, campgroundDescription }) => {
|
|
65
70
|
try {
|
|
66
71
|
const params = new URLSearchParams();
|
|
67
72
|
if (limit)
|
|
68
73
|
params.set('limit', limit.toString());
|
|
74
|
+
if (cursor)
|
|
75
|
+
params.set('cursor', cursor);
|
|
69
76
|
if (startDate)
|
|
70
77
|
params.set('startDate', startDate);
|
|
71
78
|
if (endDate)
|
|
@@ -80,13 +87,21 @@ export const placeSearchTool = (server, campertunityClient) => {
|
|
|
80
87
|
params.set('longitude', longitude.toString());
|
|
81
88
|
if (radius)
|
|
82
89
|
params.set('radius', radius.toString());
|
|
90
|
+
if (region)
|
|
91
|
+
params.set('region', region);
|
|
92
|
+
if (city)
|
|
93
|
+
params.set('city', city);
|
|
94
|
+
if (country)
|
|
95
|
+
params.set('country', country);
|
|
96
|
+
if (countryCode)
|
|
97
|
+
params.set('countryCode', countryCode);
|
|
83
98
|
if (filters)
|
|
84
99
|
params.set('filters', filters.join(','));
|
|
85
100
|
if (campgroundDescription)
|
|
86
101
|
params.set('campgroundDescription', campgroundDescription);
|
|
87
|
-
const
|
|
102
|
+
const listings = await campertunityClient.get(`/listings?${params.toString()}`);
|
|
88
103
|
return {
|
|
89
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
104
|
+
content: [{ type: 'text', text: JSON.stringify(listings), mimeType: 'application/json' }],
|
|
90
105
|
};
|
|
91
106
|
}
|
|
92
107
|
catch (error) {
|
package/package.json
CHANGED