campertunity-mcp-server 0.0.1
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 +131 -0
- package/dist/campertunity/client.js +34 -0
- package/dist/index.js +30 -0
- package/dist/tools/place_availability.js +27 -0
- package/dist/tools/place_book.js +29 -0
- package/dist/tools/place_details.js +26 -0
- package/dist/tools/place_search.js +99 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# MCP Server for Campertunity
|
|
2
|
+
|
|
3
|
+
This server implements the Model Context Protocol (MCP) for Campertunity, providing AI models with tools to interact with camping and outdoor recreation data.
|
|
4
|
+
|
|
5
|
+
## MCP Client Config
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"mcp-campertunity-local": {
|
|
11
|
+
"command": "node",
|
|
12
|
+
"args": ["/path/to/project/dist/index.js"],
|
|
13
|
+
"env": {
|
|
14
|
+
"CAMPERTUNITY_API_KEY": "your_api_key_here",
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
1. Get your API key from [https://campertunity.com/mcp](https://campertunity.com/mcp)
|
|
25
|
+
2. Set the environment variable:
|
|
26
|
+
```
|
|
27
|
+
CAMPERTUNITY_API_KEY=your_api_key_here
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Available Tools
|
|
31
|
+
|
|
32
|
+
### place-search
|
|
33
|
+
Search for camping places with various filters and criteria.
|
|
34
|
+
- **Parameters:**
|
|
35
|
+
- `limit`: Number of results (default: 50, max: 1000)
|
|
36
|
+
- `startDate`: Start date for availability (YYYY-MM-DD)
|
|
37
|
+
- `endDate`: End date for availability (YYYY-MM-DD)
|
|
38
|
+
- `adults`: Number of adults (default: 1)
|
|
39
|
+
- `children`: Number of children (default: 0)
|
|
40
|
+
- `latitude`: Center point latitude
|
|
41
|
+
- `longitude`: Center point longitude
|
|
42
|
+
- `radius`: Search radius in kilometers (default: 20)
|
|
43
|
+
- `filters`: Array of tags to filter by (see Tag enum below)
|
|
44
|
+
- `campgroundDescription`: Natural language description of desired campground features
|
|
45
|
+
|
|
46
|
+
### place-details
|
|
47
|
+
Get detailed information about a specific camping place.
|
|
48
|
+
- **Parameters:**
|
|
49
|
+
- `placeId`: ID of the place to get details for
|
|
50
|
+
|
|
51
|
+
### place-availability
|
|
52
|
+
Check availability of camping sites at a specific place.
|
|
53
|
+
- **Parameters:**
|
|
54
|
+
- `placeId`: ID of the place to check
|
|
55
|
+
- `siteIds`: Optional array of specific site IDs to check
|
|
56
|
+
- `startDate`: Start date (YYYY-MM-DD)
|
|
57
|
+
- `endDate`: End date (YYYY-MM-DD)
|
|
58
|
+
|
|
59
|
+
### place-book
|
|
60
|
+
Book a camping site.
|
|
61
|
+
- **Parameters:**
|
|
62
|
+
- `placeId`: ID of the place to book
|
|
63
|
+
- `startDate`: Start date (YYYY-MM-DD)
|
|
64
|
+
- `endDate`: End date (YYYY-MM-DD)
|
|
65
|
+
- `adults`: Number of adults (default: 1)
|
|
66
|
+
- `children`: Number of children (default: 0)
|
|
67
|
+
|
|
68
|
+
## Available Tags for Filtering
|
|
69
|
+
|
|
70
|
+
### Site Types
|
|
71
|
+
- tent
|
|
72
|
+
- rv
|
|
73
|
+
- lodging
|
|
74
|
+
- glamping
|
|
75
|
+
- cabin
|
|
76
|
+
|
|
77
|
+
### Access Types
|
|
78
|
+
- driveIn
|
|
79
|
+
- walkIn
|
|
80
|
+
- equestrian
|
|
81
|
+
- boat
|
|
82
|
+
|
|
83
|
+
### Activities
|
|
84
|
+
- biking
|
|
85
|
+
- boating
|
|
86
|
+
- fishing
|
|
87
|
+
- hiking
|
|
88
|
+
- horsebackRiding
|
|
89
|
+
- paddling
|
|
90
|
+
- windSports
|
|
91
|
+
- surfing
|
|
92
|
+
- swimming
|
|
93
|
+
- whitewaterPaddling
|
|
94
|
+
- wildlifeWatching
|
|
95
|
+
|
|
96
|
+
### Amenities
|
|
97
|
+
- picnicTable
|
|
98
|
+
- fires
|
|
99
|
+
- toilets
|
|
100
|
+
- outhouse
|
|
101
|
+
- potableWater
|
|
102
|
+
- petFriendly
|
|
103
|
+
- rvHookup
|
|
104
|
+
- rvSanitation
|
|
105
|
+
- trash
|
|
106
|
+
- showers
|
|
107
|
+
- wifi
|
|
108
|
+
- handicap
|
|
109
|
+
|
|
110
|
+
### Terrain
|
|
111
|
+
- beach
|
|
112
|
+
- cave
|
|
113
|
+
- desert
|
|
114
|
+
- forest
|
|
115
|
+
- hotSpring
|
|
116
|
+
- lake
|
|
117
|
+
- river
|
|
118
|
+
- swimmingHole
|
|
119
|
+
- waterfall
|
|
120
|
+
- creek
|
|
121
|
+
|
|
122
|
+
## Important Notice
|
|
123
|
+
|
|
124
|
+
The data provided through these tools is collected from multiple sources and enhanced with AI. To ensure data accuracy and respect intellectual property rights:
|
|
125
|
+
|
|
126
|
+
- Do not redistribute the data
|
|
127
|
+
- Do not save or cache the data
|
|
128
|
+
- Do not modify the data
|
|
129
|
+
- Always use real-time data through the server
|
|
130
|
+
|
|
131
|
+
For more information, visit [campertunity.com](https://campertunity.com)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const CAMPERTUNITY_API_URL = process.env.CAMPERTUNITY_API_URL || "https://us-central1-my-project-1517611279378.cloudfunctions.net/public/api";
|
|
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
|
+
export class CampertunityClient {
|
|
7
|
+
async get(path) {
|
|
8
|
+
const response = await fetch(`${CAMPERTUNITY_API_URL}${path}`, {
|
|
9
|
+
method: "GET",
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${CAMPERTUNITY_API_KEY}`,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Campertunity API error: ${response.statusText}`);
|
|
16
|
+
}
|
|
17
|
+
return response.json();
|
|
18
|
+
}
|
|
19
|
+
async post(path, data) {
|
|
20
|
+
const response = await fetch(`${CAMPERTUNITY_API_URL}${path}`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${CAMPERTUNITY_API_KEY}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(data),
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Campertunity API error: ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
return response.json();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export const campertunityClient = new CampertunityClient();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { CampertunityClient } from "./campertunity/client.js";
|
|
5
|
+
import { placeAvailabilityTool } from "./tools/place_availability.js";
|
|
6
|
+
import { placeBookTool } from "./tools/place_book.js";
|
|
7
|
+
import { placeDetailsTool } from "./tools/place_details.js";
|
|
8
|
+
import { placeSearchTool } from "./tools/place_search.js";
|
|
9
|
+
const campertunityClient = new CampertunityClient();
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: "campertunity-model-context-protocol-server",
|
|
12
|
+
version: "0.0.1",
|
|
13
|
+
}, {
|
|
14
|
+
capabilities: {
|
|
15
|
+
tools: {},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
placeAvailabilityTool(server, campertunityClient);
|
|
19
|
+
placeBookTool(server, campertunityClient);
|
|
20
|
+
placeDetailsTool(server, campertunityClient);
|
|
21
|
+
placeSearchTool(server, campertunityClient);
|
|
22
|
+
async function runServer() {
|
|
23
|
+
const transport = new StdioServerTransport();
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
console.error("Campertunity MCP Server running on stdio");
|
|
26
|
+
}
|
|
27
|
+
runServer().catch((error) => {
|
|
28
|
+
console.error("Fatal error in main():", error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const placeAvailabilityTool = (server, campertunityClient) => {
|
|
3
|
+
server.tool('place-availability', {
|
|
4
|
+
placeId: z.string().describe('The id of the place to check availability for.'),
|
|
5
|
+
siteIds: z.array(z.string()).optional().describe('The ids of the sites to check availability for.'),
|
|
6
|
+
startDate: z.string().describe('The start date to check availability for. Format: YYYY-MM-DD'),
|
|
7
|
+
endDate: z.string().describe('The end date to check availability for. Format: YYYY-MM-DD'),
|
|
8
|
+
}, async ({ placeId, siteIds, startDate, endDate }) => {
|
|
9
|
+
try {
|
|
10
|
+
const availability = await campertunityClient.post(`/place/availability`, {
|
|
11
|
+
placeId,
|
|
12
|
+
siteIds,
|
|
13
|
+
startDate,
|
|
14
|
+
endDate,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: JSON.stringify(availability), mimeType: 'application/json' }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: 'text', text: 'Error: ' + error.message }],
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const placeBookTool = (server, campertunityClient) => {
|
|
3
|
+
server.tool('place-book', {
|
|
4
|
+
placeId: z.string().describe('The id of the place to book.'),
|
|
5
|
+
startDate: z.string().optional().describe('The start date of the booking. Format: YYYY-MM-DD'),
|
|
6
|
+
endDate: z.string().optional().describe('The end date of the booking. Format: YYYY-MM-DD'),
|
|
7
|
+
adults: z.number().default(1).describe('Number of adults. Default is 1.'),
|
|
8
|
+
children: z.number().default(0).describe('Number of children. Default is 0.'),
|
|
9
|
+
}, async ({ placeId, startDate, endDate, adults, children }) => {
|
|
10
|
+
try {
|
|
11
|
+
const availability = await campertunityClient.post(`/place/book`, {
|
|
12
|
+
placeId,
|
|
13
|
+
startDate,
|
|
14
|
+
endDate,
|
|
15
|
+
adults,
|
|
16
|
+
children,
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: 'text', text: JSON.stringify(availability), mimeType: 'application/json' }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: 'Error: ' + error.message }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// NOTE: This is a tool since tools are more supported by the MCP protocol
|
|
3
|
+
export const placeDetailsTool = (server, campertunityClient) => {
|
|
4
|
+
server.tool('place-details', {
|
|
5
|
+
placeId: z.string().describe('The id of the place to get details for.'),
|
|
6
|
+
}, async ({ placeId }) => {
|
|
7
|
+
try {
|
|
8
|
+
const place = await campertunityClient.get(`/place/${placeId}`);
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: JSON.stringify(place),
|
|
14
|
+
mimeType: 'application/json',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: 'Error: ' + error.message }],
|
|
22
|
+
isError: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export var Tag;
|
|
3
|
+
(function (Tag) {
|
|
4
|
+
// SiteType
|
|
5
|
+
Tag["tent"] = "tent";
|
|
6
|
+
Tag["rv"] = "rv";
|
|
7
|
+
Tag["lodging"] = "lodging";
|
|
8
|
+
Tag["glamping"] = "glamping";
|
|
9
|
+
Tag["cabin"] = "cabin";
|
|
10
|
+
// AccessType
|
|
11
|
+
Tag["driveIn"] = "driveIn";
|
|
12
|
+
Tag["walkIn"] = "walkIn";
|
|
13
|
+
Tag["equestrian"] = "equestrian";
|
|
14
|
+
Tag["boat"] = "boat";
|
|
15
|
+
// Activities
|
|
16
|
+
Tag["biking"] = "biking";
|
|
17
|
+
Tag["boating"] = "boating";
|
|
18
|
+
Tag["fishing"] = "fishing";
|
|
19
|
+
Tag["hiking"] = "hiking";
|
|
20
|
+
Tag["horsebackRiding"] = "horsebackRiding";
|
|
21
|
+
Tag["paddling"] = "paddling";
|
|
22
|
+
Tag["windSports"] = "windSports";
|
|
23
|
+
Tag["surfing"] = "surfing";
|
|
24
|
+
Tag["swimming"] = "swimming";
|
|
25
|
+
Tag["whitewaterPaddling"] = "whitewaterPaddling";
|
|
26
|
+
Tag["wildlifeWatching"] = "wildlifeWatching";
|
|
27
|
+
// Amenities
|
|
28
|
+
Tag["picnicTable"] = "picnicTable";
|
|
29
|
+
Tag["fires"] = "fires";
|
|
30
|
+
Tag["toilets"] = "toilets";
|
|
31
|
+
Tag["outhouse"] = "outhouse";
|
|
32
|
+
Tag["potableWater"] = "potableWater";
|
|
33
|
+
Tag["petFriendly"] = "petFriendly";
|
|
34
|
+
Tag["rvHookup"] = "rvHookup";
|
|
35
|
+
Tag["rvSanitation"] = "rvSanitation";
|
|
36
|
+
Tag["trash"] = "trash";
|
|
37
|
+
Tag["showers"] = "showers";
|
|
38
|
+
Tag["wifi"] = "wifi";
|
|
39
|
+
Tag["handicap"] = "handicap";
|
|
40
|
+
// Terrain
|
|
41
|
+
Tag["beach"] = "beach";
|
|
42
|
+
Tag["cave"] = "cave";
|
|
43
|
+
Tag["desert"] = "desert";
|
|
44
|
+
Tag["forest"] = "forest";
|
|
45
|
+
Tag["hotSpring"] = "hotSpring";
|
|
46
|
+
Tag["lake"] = "lake";
|
|
47
|
+
Tag["river"] = "river";
|
|
48
|
+
Tag["swimmingHole"] = "swimmingHole";
|
|
49
|
+
Tag["waterfall"] = "waterfall";
|
|
50
|
+
Tag["creek"] = "creek";
|
|
51
|
+
})(Tag || (Tag = {}));
|
|
52
|
+
export const placeSearchTool = (server, campertunityClient) => {
|
|
53
|
+
server.tool('place-search', {
|
|
54
|
+
limit: z.number().default(50).optional().describe('Number of places to return. Default is 50, max is 1000.'),
|
|
55
|
+
startDate: z.string().optional().describe('Start date for availability search. Format: YYYY-MM-DD'),
|
|
56
|
+
endDate: z.string().optional().describe('End date for availability search. Format: YYYY-MM-DD'),
|
|
57
|
+
adults: z.number().optional().describe('Number of adults. Default is 1.'),
|
|
58
|
+
children: z.number().optional().describe('Number of children. Default is 0.'),
|
|
59
|
+
latitude: z.number().optional().describe('Latitude to filter by.'),
|
|
60
|
+
longitude: z.number().optional().describe('Longitude to filter by.'),
|
|
61
|
+
radius: z.number().optional().default(20).describe('Radius to filter by (in km).'),
|
|
62
|
+
filters: z.array(z.enum(Object.values(Tag))).optional().describe('Filter out places that have specific tags.'),
|
|
63
|
+
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 }) => {
|
|
65
|
+
try {
|
|
66
|
+
const params = new URLSearchParams();
|
|
67
|
+
if (limit)
|
|
68
|
+
params.set('limit', limit.toString());
|
|
69
|
+
if (startDate)
|
|
70
|
+
params.set('startDate', startDate);
|
|
71
|
+
if (endDate)
|
|
72
|
+
params.set('endDate', endDate);
|
|
73
|
+
if (adults)
|
|
74
|
+
params.set('adults', adults.toString());
|
|
75
|
+
if (children)
|
|
76
|
+
params.set('children', children.toString());
|
|
77
|
+
if (latitude)
|
|
78
|
+
params.set('latitude', latitude.toString());
|
|
79
|
+
if (longitude)
|
|
80
|
+
params.set('longitude', longitude.toString());
|
|
81
|
+
if (radius)
|
|
82
|
+
params.set('radius', radius.toString());
|
|
83
|
+
if (filters)
|
|
84
|
+
params.set('filters', filters.join(','));
|
|
85
|
+
if (campgroundDescription)
|
|
86
|
+
params.set('campgroundDescription', campgroundDescription);
|
|
87
|
+
const places = await campertunityClient.get(`/place/search?${params.toString()}`);
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: 'text', text: JSON.stringify(places), mimeType: 'application/json' }],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: 'text', text: 'Error: ' + error.message }],
|
|
95
|
+
isError: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "campertunity-mcp-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MCP Server for Campertunity - A Model Context Protocol server for interacting with camping and outdoor recreation data",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-campertunity-server": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
14
|
+
"prepare": "npm run build",
|
|
15
|
+
"watch": "tsc --watch",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"campertunity",
|
|
21
|
+
"camping",
|
|
22
|
+
"outdoor",
|
|
23
|
+
"recreation",
|
|
24
|
+
"ai",
|
|
25
|
+
"model-context-protocol"
|
|
26
|
+
],
|
|
27
|
+
"author": "Campertunity",
|
|
28
|
+
"license": "AGPL-3.0",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/campertunity/mcp-server"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/campertunity/mcp-server/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://campertunity.com",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.7.0",
|
|
39
|
+
"@types/node": "^22"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22",
|
|
43
|
+
"shx": "^0.3.4",
|
|
44
|
+
"typescript": "^5.8.2"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|