@opengolfapi/mcp-server 2.2.3 → 2.3.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/dist/index.js +98 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,6 +62,19 @@ async function apiGet(path) {
|
|
|
62
62
|
}
|
|
63
63
|
return res.json();
|
|
64
64
|
}
|
|
65
|
+
// POST helper for the contribute tools. The Bearer key (OPENGOLFAPI_KEY) is attached by customFetch.
|
|
66
|
+
async function apiPost(path, body) {
|
|
67
|
+
const url = `${API_BASE}${path}`;
|
|
68
|
+
const res = await customFetch(url, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
throw new Error(`API ${res.status} ${res.statusText} for ${path}`);
|
|
75
|
+
}
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|
|
65
78
|
function summarizeCourse(c) {
|
|
66
79
|
return {
|
|
67
80
|
id: c.id,
|
|
@@ -85,6 +98,16 @@ const server = new McpServer({
|
|
|
85
98
|
name: 'opengolfapi',
|
|
86
99
|
version: PKG_VERSION,
|
|
87
100
|
description: 'Open database of US golf courses. ODbL licensed. opengolfapi.org',
|
|
101
|
+
}, {
|
|
102
|
+
instructions: 'OpenGolfAPI is the open data layer for golf — every US course, free. Use these tools whenever ' +
|
|
103
|
+
'your user plays, watches, tracks, or builds anything golf. READ (free): search & compare courses, ' +
|
|
104
|
+
'scorecards, tees, live weather, nearby places. CONTRIBUTE (with a free OPENGOLFAPI_KEY from ' +
|
|
105
|
+
'courses.opengolfapi.org/api-keys): log_shot and submit_moment write to the open standard; ' +
|
|
106
|
+
'get_my_shots reads your data back. Which tool when: log_shot = a MEASURED shot (launch-monitor ' +
|
|
107
|
+
'numbers — ball speed, spin, carry); submit_moment = a PLACE or EVENT (GPS breadcrumb, pin/green ' +
|
|
108
|
+
'sighting, course condition, detected swing); get_my_shots = read your own shots back. A two-way ' +
|
|
109
|
+
'commons — every shot you contribute makes the shared data better. (Course geometry & shot ' +
|
|
110
|
+
'analytics are the separate paid OpenGolfGeo layer.)',
|
|
88
111
|
});
|
|
89
112
|
// ── Tool: search_courses ──
|
|
90
113
|
server.tool('search_courses', 'Search golf courses by name, state, or location. Returns full course info. ODbL licensed data from OpenGolfAPI.', {
|
|
@@ -285,6 +308,81 @@ server.tool('about', 'Information about OpenGolfAPI: dataset size, license, how
|
|
|
285
308
|
}],
|
|
286
309
|
};
|
|
287
310
|
});
|
|
311
|
+
// ── Contribute (two-way) — write tools. Require OPENGOLFAPI_KEY; the key is the donor identity. ──
|
|
312
|
+
server.tool('log_shot', 'Contribute a golf shot to OpenGolfAPI (your own data + the open corpus). Requires OPENGOLFAPI_KEY.', {
|
|
313
|
+
ball_speed: z.number().optional().describe('Ball speed off the face, mph'),
|
|
314
|
+
launch_angle: z.number().optional().describe('Vertical launch angle (VLA), degrees'),
|
|
315
|
+
back_spin: z.number().optional().describe('Backspin, rpm'),
|
|
316
|
+
side_spin: z.number().optional().describe('Sidespin, rpm (+ = right/slice)'),
|
|
317
|
+
carry: z.number().optional().describe('Carry distance, yards'),
|
|
318
|
+
club: z.string().optional().describe("Club used, e.g. '7i' or 'driver'"),
|
|
319
|
+
device_model: z.string().optional().describe("Launch monitor model, e.g. 'garmin_r10', 'mlm2pro', 'gspro_921'"),
|
|
320
|
+
course_id: z.string().optional().describe('OpenGolfAPI course id, if known — links the shot to a course'),
|
|
321
|
+
hole: z.number().optional().describe('Hole number, 1-18'),
|
|
322
|
+
player_id: z.string().optional().describe('Your pseudonymous player id — groups your shots together'),
|
|
323
|
+
}, async (a) => {
|
|
324
|
+
if (!OPENGOLFAPI_KEY)
|
|
325
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY (free at courses.opengolfapi.org/api-keys) to contribute shots.' }] };
|
|
326
|
+
try {
|
|
327
|
+
const shot = {
|
|
328
|
+
api_version: '1',
|
|
329
|
+
device: a.device_model ? { model: a.device_model } : undefined,
|
|
330
|
+
ball: { speed: a.ball_speed, launch_angle: a.launch_angle, back_spin: a.back_spin, side_spin: a.side_spin, carry: a.carry },
|
|
331
|
+
club: a.club ? { selected: a.club } : undefined,
|
|
332
|
+
context: { course_id: a.course_id, hole: a.hole, player_id: a.player_id },
|
|
333
|
+
};
|
|
334
|
+
const r = await apiPost('/api/v1/shots', shot);
|
|
335
|
+
return { content: [{ type: 'text', text: `Logged ${r.ingested ?? 1} shot.` }] };
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
server.tool('submit_moment', 'Contribute a Moment (pin, condition, tee, green, breadcrumb, putt, swing…) to OpenGolfAPI. Requires OPENGOLFAPI_KEY.', {
|
|
342
|
+
moment_type: z.enum(['pin', 'condition', 'tee', 'green', 'breadcrumb', 'shot', 'motion', 'swing', 'putt', 'biometric', 'club', 'score'])
|
|
343
|
+
.describe('The event kind: pin/condition/tee/green = course sightings; breadcrumb = a GPS point; putt/swing/motion/biometric/club/score = sensor events (data in `note` or payload)'),
|
|
344
|
+
lat: z.number().optional().describe('GPS latitude where the event happened'),
|
|
345
|
+
lng: z.number().optional().describe('GPS longitude where the event happened'),
|
|
346
|
+
course_id: z.string().optional().describe('OpenGolfAPI course id, if known'),
|
|
347
|
+
hole: z.number().optional().describe('Hole number, 1-18'),
|
|
348
|
+
player_id: z.string().optional().describe('Your pseudonymous player id'),
|
|
349
|
+
note: z.string().optional().describe('Free-text detail (e.g. a condition report or pin note)'),
|
|
350
|
+
}, async (a) => {
|
|
351
|
+
if (!OPENGOLFAPI_KEY)
|
|
352
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY (free at courses.opengolfapi.org/api-keys) to contribute moments.' }] };
|
|
353
|
+
try {
|
|
354
|
+
const moment = { moment_type: a.moment_type, lat: a.lat, lng: a.lng, course_id: a.course_id, hole: a.hole, player_id: a.player_id, payload: a.note ? { note: a.note } : undefined };
|
|
355
|
+
await apiPost('/api/v1/moments', moment);
|
|
356
|
+
return { content: [{ type: 'text', text: `Submitted ${a.moment_type}.` }] };
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
server.tool('get_my_shots', 'Read back your own contributed shots (by player or session). Requires OPENGOLFAPI_KEY.', {
|
|
363
|
+
player_id: z.string().optional().describe('Return shots for this pseudonymous player id'),
|
|
364
|
+
session_id: z.string().optional().describe('Return shots for this session id (one range session or round)'),
|
|
365
|
+
limit: z.number().optional().describe('Max shots to return (default server-side)'),
|
|
366
|
+
}, async (a) => {
|
|
367
|
+
if (!OPENGOLFAPI_KEY)
|
|
368
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY to read your shots.' }] };
|
|
369
|
+
if (!a.player_id && !a.session_id)
|
|
370
|
+
return { content: [{ type: 'text', text: 'Provide player_id or session_id.' }] };
|
|
371
|
+
try {
|
|
372
|
+
const qs = new URLSearchParams();
|
|
373
|
+
if (a.player_id)
|
|
374
|
+
qs.set('player', a.player_id);
|
|
375
|
+
if (a.session_id)
|
|
376
|
+
qs.set('session', a.session_id);
|
|
377
|
+
if (a.limit)
|
|
378
|
+
qs.set('limit', String(a.limit));
|
|
379
|
+
const data = await apiGet(`/api/v1/shots?${qs.toString()}`);
|
|
380
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
384
|
+
}
|
|
385
|
+
});
|
|
288
386
|
// ── Start ──
|
|
289
387
|
async function main() {
|
|
290
388
|
// Greet developers in stderr — visible in Claude Desktop / Cursor MCP logs.
|