@opengolfapi/mcp-server 2.2.2 → 2.3.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 +16 -8
- package/dist/index.js +94 -6
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -63,9 +63,21 @@ Donor tiers raise the daily limit further (10k / 50k / 250k / 1M).
|
|
|
63
63
|
|
|
64
64
|
## Telemetry
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
This server reports unhandled errors to the OpenGolfAPI Sentry project by default so the maintainers can catch bugs that hit real users. **No request bodies, no API keys, no PII** — only stack traces of exceptions thrown inside the server process. Quotas and sampling live in the Sentry project config; the DSN is an ingest endpoint, not a secret.
|
|
67
|
+
|
|
68
|
+
To opt out completely:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
OPENGOLFAPI_DISABLE_TELEMETRY=1 npx @opengolfapi/mcp-server
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
To send to your own Sentry project instead (overrides the default DSN):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
SENTRY_DSN=https://your-dsn@sentry.io/... npx @opengolfapi/mcp-server
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Example MCP config:
|
|
69
81
|
|
|
70
82
|
```json
|
|
71
83
|
{
|
|
@@ -74,17 +86,13 @@ running the server.
|
|
|
74
86
|
"command": "npx",
|
|
75
87
|
"args": ["@opengolfapi/mcp-server"],
|
|
76
88
|
"env": {
|
|
77
|
-
"OPENGOLFAPI_KEY": "ogapi_yourkeyhere"
|
|
78
|
-
"SENTRY_DSN": "https://<key>@o<org>.ingest.sentry.io/<project>"
|
|
89
|
+
"OPENGOLFAPI_KEY": "ogapi_yourkeyhere"
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
```
|
|
84
95
|
|
|
85
|
-
When `SENTRY_DSN` is unset the SDK is a complete no-op — nothing is initialized
|
|
86
|
-
and no network calls are made.
|
|
87
|
-
|
|
88
96
|
## License
|
|
89
97
|
|
|
90
98
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -12,13 +12,20 @@
|
|
|
12
12
|
* Install: npx @opengolfapi/mcp-server
|
|
13
13
|
*/
|
|
14
14
|
// Sentry must initialize BEFORE any other imports that could throw, so any
|
|
15
|
-
// load-time error in transitive deps still gets captured.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// load-time error in transitive deps still gets captured.
|
|
16
|
+
//
|
|
17
|
+
// Default DSN ships baked-in so we get bug telemetry from every install — a
|
|
18
|
+
// Sentry DSN is designed to be public (it's an ingest endpoint, not a secret).
|
|
19
|
+
// Users can override with SENTRY_DSN, or opt out completely with
|
|
20
|
+
// OPENGOLFAPI_DISABLE_TELEMETRY=1.
|
|
18
21
|
import * as Sentry from '@sentry/node';
|
|
19
|
-
|
|
22
|
+
const DEFAULT_SENTRY_DSN = 'https://2cb261cd86bbfe9e105309d3c2edbced@o4511071885000704.ingest.us.sentry.io/4511345201315840';
|
|
23
|
+
const SENTRY_DSN_ACTIVE = process.env.OPENGOLFAPI_DISABLE_TELEMETRY
|
|
24
|
+
? ''
|
|
25
|
+
: (process.env.SENTRY_DSN || DEFAULT_SENTRY_DSN);
|
|
26
|
+
if (SENTRY_DSN_ACTIVE) {
|
|
20
27
|
Sentry.init({
|
|
21
|
-
dsn:
|
|
28
|
+
dsn: SENTRY_DSN_ACTIVE,
|
|
22
29
|
tracesSampleRate: 0.1,
|
|
23
30
|
release: `opengolfapi-mcp-server@${process.env.npm_package_version || 'unknown'}`,
|
|
24
31
|
});
|
|
@@ -27,7 +34,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
27
34
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
28
35
|
import { z } from 'zod';
|
|
29
36
|
// Package version — used in User-Agent so the API can identify MCP traffic.
|
|
30
|
-
const PKG_VERSION = '2.2.
|
|
37
|
+
const PKG_VERSION = '2.2.3';
|
|
31
38
|
const API_BASE = process.env.OPENGOLFAPI_BASE ?? 'https://api.opengolfapi.org';
|
|
32
39
|
// Optional API key for higher rate limits. Anonymous (no key) still works
|
|
33
40
|
// at 1k req/day per IP. With a free key from courses.opengolfapi.org/api-keys,
|
|
@@ -55,6 +62,19 @@ async function apiGet(path) {
|
|
|
55
62
|
}
|
|
56
63
|
return res.json();
|
|
57
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
|
+
}
|
|
58
78
|
function summarizeCourse(c) {
|
|
59
79
|
return {
|
|
60
80
|
id: c.id,
|
|
@@ -78,6 +98,13 @@ const server = new McpServer({
|
|
|
78
98
|
name: 'opengolfapi',
|
|
79
99
|
version: PKG_VERSION,
|
|
80
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. A two-way commons — every shot you contribute makes the shared ' +
|
|
107
|
+
'data better. (Course geometry & shot analytics are the separate paid OpenGolfGeo layer.)',
|
|
81
108
|
});
|
|
82
109
|
// ── Tool: search_courses ──
|
|
83
110
|
server.tool('search_courses', 'Search golf courses by name, state, or location. Returns full course info. ODbL licensed data from OpenGolfAPI.', {
|
|
@@ -278,6 +305,67 @@ server.tool('about', 'Information about OpenGolfAPI: dataset size, license, how
|
|
|
278
305
|
}],
|
|
279
306
|
};
|
|
280
307
|
});
|
|
308
|
+
// ── Contribute (two-way) — write tools. Require OPENGOLFAPI_KEY; the key is the donor identity. ──
|
|
309
|
+
server.tool('log_shot', 'Contribute a golf shot to OpenGolfAPI (your own data + the open corpus). Requires OPENGOLFAPI_KEY.', {
|
|
310
|
+
ball_speed: z.number().optional(), launch_angle: z.number().optional(),
|
|
311
|
+
back_spin: z.number().optional(), side_spin: z.number().optional(), carry: z.number().optional(),
|
|
312
|
+
club: z.string().optional(), device_model: z.string().optional(),
|
|
313
|
+
course_id: z.string().optional(), hole: z.number().optional(), player_id: z.string().optional(),
|
|
314
|
+
}, async (a) => {
|
|
315
|
+
if (!OPENGOLFAPI_KEY)
|
|
316
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY (free at courses.opengolfapi.org/api-keys) to contribute shots.' }] };
|
|
317
|
+
try {
|
|
318
|
+
const shot = {
|
|
319
|
+
api_version: '1',
|
|
320
|
+
device: a.device_model ? { model: a.device_model } : undefined,
|
|
321
|
+
ball: { speed: a.ball_speed, launch_angle: a.launch_angle, back_spin: a.back_spin, side_spin: a.side_spin, carry: a.carry },
|
|
322
|
+
club: a.club ? { selected: a.club } : undefined,
|
|
323
|
+
context: { course_id: a.course_id, hole: a.hole, player_id: a.player_id },
|
|
324
|
+
};
|
|
325
|
+
const r = await apiPost('/api/v1/shots', shot);
|
|
326
|
+
return { content: [{ type: 'text', text: `Logged ${r.ingested ?? 1} shot.` }] };
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
server.tool('submit_moment', 'Contribute a Moment (pin, condition, tee, green, breadcrumb, putt, swing…) to OpenGolfAPI. Requires OPENGOLFAPI_KEY.', {
|
|
333
|
+
moment_type: z.enum(['pin', 'condition', 'tee', 'green', 'breadcrumb', 'shot', 'motion', 'swing', 'putt', 'biometric', 'club', 'score']),
|
|
334
|
+
lat: z.number().optional(), lng: z.number().optional(),
|
|
335
|
+
course_id: z.string().optional(), hole: z.number().optional(), player_id: z.string().optional(),
|
|
336
|
+
note: z.string().optional(),
|
|
337
|
+
}, async (a) => {
|
|
338
|
+
if (!OPENGOLFAPI_KEY)
|
|
339
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY (free at courses.opengolfapi.org/api-keys) to contribute moments.' }] };
|
|
340
|
+
try {
|
|
341
|
+
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 };
|
|
342
|
+
await apiPost('/api/v1/moments', moment);
|
|
343
|
+
return { content: [{ type: 'text', text: `Submitted ${a.moment_type}.` }] };
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
server.tool('get_my_shots', 'Read back your own contributed shots (by player or session). Requires OPENGOLFAPI_KEY.', { player_id: z.string().optional(), session_id: z.string().optional(), limit: z.number().optional() }, async (a) => {
|
|
350
|
+
if (!OPENGOLFAPI_KEY)
|
|
351
|
+
return { content: [{ type: 'text', text: 'Set OPENGOLFAPI_KEY to read your shots.' }] };
|
|
352
|
+
if (!a.player_id && !a.session_id)
|
|
353
|
+
return { content: [{ type: 'text', text: 'Provide player_id or session_id.' }] };
|
|
354
|
+
try {
|
|
355
|
+
const qs = new URLSearchParams();
|
|
356
|
+
if (a.player_id)
|
|
357
|
+
qs.set('player', a.player_id);
|
|
358
|
+
if (a.session_id)
|
|
359
|
+
qs.set('session', a.session_id);
|
|
360
|
+
if (a.limit)
|
|
361
|
+
qs.set('limit', String(a.limit));
|
|
362
|
+
const data = await apiGet(`/api/v1/shots?${qs.toString()}`);
|
|
363
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
367
|
+
}
|
|
368
|
+
});
|
|
281
369
|
// ── Start ──
|
|
282
370
|
async function main() {
|
|
283
371
|
// Greet developers in stderr — visible in Claude Desktop / Cursor MCP logs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengolfapi/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Open MCP server for AI agents to query the OpenGolfAPI dataset (14,708 US golf courses, ODbL)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
-
"prepublishOnly": "npm
|
|
16
|
+
"prepublishOnly": "npm test",
|
|
17
|
+
"test": "npm run build && vitest run"
|
|
17
18
|
},
|
|
18
19
|
"repository": {
|
|
19
20
|
"type": "git",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/node": "^20.0.0",
|
|
41
|
-
"typescript": "^5.0.0"
|
|
42
|
+
"typescript": "^5.0.0",
|
|
43
|
+
"vitest": "^4.1.5"
|
|
42
44
|
}
|
|
43
45
|
}
|