@opengolfapi/mcp-server 1.0.0 → 2.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @opengolfapi/mcp-server
2
2
 
3
- Free MCP server for AI agents to query the OpenGolfAPI dataset (16,908 US golf courses).
3
+ Open MCP server for AI agents to query the OpenGolfAPI dataset (16,908 US golf courses). All data is ODbL licensed and open.
4
4
 
5
5
  ## Install
6
6
 
@@ -24,10 +24,11 @@ Add to your MCP client config:
24
24
 
25
25
  ## Tools
26
26
 
27
- - `search_courses(query, state?)` — find courses by name + state
28
- - `get_course(id)` — full free-tier course record with scorecard
29
-
30
- For premium data (per-tee ratings, climate, nearby POIs, booking links), see **[GolfAGI](https://golfagi.com)**.
27
+ - `search_courses(query, state?, lat?, lng?, radius_mi?)` — find courses by name, state, or location
28
+ - `get_course(id)` — full course record with scorecard (par + handicap index per hole)
29
+ - `get_tees(id)` — all tee sets with ratings, slopes, and yardages
30
+ - `get_climate(id)` monthly climate normals for the course location
31
+ - `get_nearby(id)` — nearby POIs (hotels, restaurants, airports)
31
32
 
32
33
  ## License
33
34
 
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * OpenGolfAPI MCP Server — Free tier.
3
+ * OpenGolfAPI MCP Server
4
4
  *
5
- * 2 tools: search_courses, get_course
6
- * Returns only open/free fields.
5
+ * Tools: search_courses, get_course, get_tees, get_climate, get_nearby
6
+ * All data is open — ODbL licensed.
7
7
  *
8
8
  * Install: npx @opengolfapi/mcp-server
9
- * Or connect via: npx tsx mcp-server/opengolf.ts
10
9
  */
11
10
  export {};
package/dist/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * OpenGolfAPI MCP Server — Free tier.
3
+ * OpenGolfAPI MCP Server
4
4
  *
5
- * 2 tools: search_courses, get_course
6
- * Returns only open/free fields.
5
+ * Tools: search_courses, get_course, get_tees, get_climate, get_nearby
6
+ * All data is open — ODbL licensed.
7
7
  *
8
8
  * Install: npx @opengolfapi/mcp-server
9
- * Or connect via: npx tsx mcp-server/opengolf.ts
10
9
  */
11
10
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -19,14 +18,13 @@ if (!SUPABASE_KEY) {
19
18
  process.exit(1);
20
19
  }
21
20
  const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
22
- const FREE_FIELDS = 'id, course_name, latitude, longitude, state, city, course_type, par_total, phone, website, year_built, address, postal_code';
23
21
  const server = new McpServer({
24
22
  name: 'opengolfapi',
25
- version: '1.0.0',
26
- description: 'Open database of 17,000+ US golf courses. Free, ODbL licensed. opengolfapi.org',
23
+ version: '2.0.0',
24
+ description: 'Open database of 17,000+ US golf courses. ODbL licensed. opengolfapi.org',
27
25
  });
28
26
  // ── Tool: search_courses ──
29
- server.tool('search_courses', 'Search golf courses by name, state, or location. Returns basic course info. Free, ODbL licensed data from OpenGolfAPI.', {
27
+ server.tool('search_courses', 'Search golf courses by name, state, or location. Returns full course info. ODbL licensed data from OpenGolfAPI.', {
30
28
  lat: z.number().optional().describe('Latitude for geo search'),
31
29
  lng: z.number().optional().describe('Longitude for geo search'),
32
30
  radius_mi: z.number().optional().default(25).describe('Search radius in miles'),
@@ -34,7 +32,7 @@ server.tool('search_courses', 'Search golf courses by name, state, or location.
34
32
  state: z.string().optional().describe('2-letter US state code'),
35
33
  limit: z.number().optional().default(10).describe('Max results'),
36
34
  }, async ({ lat, lng, radius_mi, query: q, state, limit }) => {
37
- let query = supabase.from('golf_courses').select(FREE_FIELDS);
35
+ let query = supabase.from('golf_courses').select('*');
38
36
  if (q)
39
37
  query = query.ilike('course_name', `%${q}%`);
40
38
  if (state)
@@ -57,8 +55,13 @@ server.tool('search_courses', 'Search golf courses by name, state, or location.
57
55
  lng: c.longitude,
58
56
  type: c.course_type,
59
57
  par: c.par_total,
58
+ total_yardage: c.total_yardage,
60
59
  phone: c.phone,
61
60
  website: c.website,
61
+ architect: c.architect,
62
+ year_built: c.year_built,
63
+ address: c.address,
64
+ postal_code: c.postal_code,
62
65
  }));
63
66
  return {
64
67
  content: [{
@@ -67,18 +70,17 @@ server.tool('search_courses', 'Search golf courses by name, state, or location.
67
70
  courses,
68
71
  total: courses.length,
69
72
  source: 'OpenGolfAPI (opengolfapi.org) — ODbL licensed',
70
- upgrade: 'Tee ratings, climate, weather, booking → golfagi.com/api',
71
73
  }, null, 2),
72
74
  }],
73
75
  };
74
76
  });
75
77
  // ── Tool: get_course ──
76
- server.tool('get_course', 'Get detailed golf course info including scorecard. Free, ODbL licensed. For tee ratings, climate, weather, booking → golfagi.com/api', {
78
+ server.tool('get_course', 'Get detailed golf course info including full scorecard with par and handicap index per hole. ODbL licensed.', {
77
79
  course_id: z.string().describe('Course UUID from search results'),
78
80
  }, async ({ course_id }) => {
79
81
  const [courseRes, holesRes] = await Promise.all([
80
- supabase.from('golf_courses').select(FREE_FIELDS).eq('id', course_id).single(),
81
- supabase.from('golf_course_holes').select('hole_number, par').eq('course_id', course_id).not('par', 'is', null).order('hole_number'),
82
+ supabase.from('golf_courses').select('*').eq('id', course_id).single(),
83
+ supabase.from('golf_course_holes').select('hole_number, par, handicap_index').eq('course_id', course_id).not('par', 'is', null).order('hole_number'),
82
84
  ]);
83
85
  if (courseRes.error || !courseRes.data) {
84
86
  return { content: [{ type: 'text', text: 'Course not found' }] };
@@ -87,6 +89,7 @@ server.tool('get_course', 'Get detailed golf course info including scorecard. Fr
87
89
  const scorecard = (holesRes.data ?? []).map(h => ({
88
90
  hole: h.hole_number,
89
91
  par: h.par,
92
+ handicap_index: h.handicap_index,
90
93
  }));
91
94
  return {
92
95
  content: [{
@@ -100,19 +103,78 @@ server.tool('get_course', 'Get detailed golf course info including scorecard. Fr
100
103
  lng: c.longitude,
101
104
  type: c.course_type,
102
105
  par: c.par_total,
106
+ total_yardage: c.total_yardage,
103
107
  holes: scorecard.length,
104
108
  phone: c.phone,
105
109
  website: c.website,
110
+ architect: c.architect,
106
111
  year_built: c.year_built,
107
112
  address: c.address,
108
113
  postal_code: c.postal_code,
109
114
  scorecard,
110
115
  source: 'OpenGolfAPI (opengolfapi.org) — ODbL licensed',
111
- upgrade: 'Tee ratings, slopes, yardages, climate, weather, nearby hotels, booking → golfagi.com/api',
112
116
  }, null, 2),
113
117
  }],
114
118
  };
115
119
  });
120
+ // ── Tool: get_tees ──
121
+ server.tool('get_tees', 'Get all tee sets for a course including ratings, slopes, and yardages per tee. ODbL licensed.', {
122
+ course_id: z.string().describe('Course UUID'),
123
+ }, async ({ course_id }) => {
124
+ const { data, error } = await supabase
125
+ .from('golf_course_tees')
126
+ .select('*')
127
+ .eq('course_id', course_id)
128
+ .order('total_yardage', { ascending: false });
129
+ if (error) {
130
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
131
+ }
132
+ return {
133
+ content: [{
134
+ type: 'text',
135
+ text: JSON.stringify({ tees: data ?? [], source: 'OpenGolfAPI (opengolfapi.org) — ODbL licensed' }, null, 2),
136
+ }],
137
+ };
138
+ });
139
+ // ── Tool: get_climate ──
140
+ server.tool('get_climate', 'Get monthly climate normals for a course (temperature, precipitation, playability). ODbL licensed.', {
141
+ course_id: z.string().describe('Course UUID'),
142
+ }, async ({ course_id }) => {
143
+ const { data, error } = await supabase
144
+ .from('golf_course_climate')
145
+ .select('*')
146
+ .eq('course_id', course_id)
147
+ .maybeSingle();
148
+ if (error) {
149
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
150
+ }
151
+ return {
152
+ content: [{
153
+ type: 'text',
154
+ text: JSON.stringify({ climate: data, source: 'OpenGolfAPI (opengolfapi.org) — ODbL licensed' }, null, 2),
155
+ }],
156
+ };
157
+ });
158
+ // ── Tool: get_nearby ──
159
+ server.tool('get_nearby', 'Get nearby points of interest for a course (hotels, restaurants, airports) within ~20 miles. ODbL licensed.', {
160
+ course_id: z.string().describe('Course UUID'),
161
+ }, async ({ course_id }) => {
162
+ const { data, error } = await supabase
163
+ .from('golf_course_nearby')
164
+ .select('*')
165
+ .eq('course_id', course_id)
166
+ .order('distance_miles')
167
+ .limit(20);
168
+ if (error) {
169
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
170
+ }
171
+ return {
172
+ content: [{
173
+ type: 'text',
174
+ text: JSON.stringify({ nearby: data ?? [], source: 'OpenGolfAPI (opengolfapi.org) — ODbL licensed' }, null, 2),
175
+ }],
176
+ };
177
+ });
116
178
  // ── Start ──
117
179
  async function main() {
118
180
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@opengolfapi/mcp-server",
3
- "version": "1.0.0",
4
- "description": "Free MCP server for AI agents to query the OpenGolfAPI dataset",
3
+ "version": "2.0.0",
4
+ "description": "Open MCP server for AI agents to query the OpenGolfAPI dataset",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opengolfapi-mcp": "dist/index.js"