football-docs 0.2.0 → 0.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 +5 -1
- package/data/docs.db +0 -0
- package/dist/index.js +77 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Searchable football data provider documentation for AI coding agents. Like [Context7](https://context7.com) for football data.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Who it's for:** Developers and analysts who use AI coding tools (Claude Code, Cursor, VS Code Copilot, Windsurf) to work with football data. Works with any tool that supports MCP.
|
|
6
|
+
|
|
7
|
+
**What it does:** Gives your AI agent a searchable index of documentation for 16 football data providers — event types, qualifier IDs, coordinate systems, API endpoints, data models, and cross-provider comparisons. Your agent looks up the real docs instead of guessing from training data.
|
|
8
|
+
|
|
9
|
+
**Why not just let the AI figure it out?** LLMs get football data specifics wrong constantly — Opta qualifier IDs, StatsBomb coordinate ranges, API endpoint URLs, library method signatures. These are mutable facts that change across versions. football-docs gives the agent verified, sourced documentation with provenance tracking so you know where every answer came from.
|
|
6
10
|
|
|
7
11
|
MCP ([Model Context Protocol](https://modelcontextprotocol.io)) is a standard for connecting AI coding tools to external data sources.
|
|
8
12
|
|
package/data/docs.db
CHANGED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ server.tool("search_docs", "Search football data provider documentation. Use for
|
|
|
79
79
|
.optional()
|
|
80
80
|
.default(10)
|
|
81
81
|
.describe("Maximum number of results to return (default 10)"),
|
|
82
|
-
}, async ({ query, provider, max_results }) => {
|
|
82
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async ({ query, provider, max_results }) => {
|
|
83
83
|
const db = openDb();
|
|
84
84
|
try {
|
|
85
85
|
const safeQuery = sanitiseFtsQuery(query);
|
|
@@ -131,7 +131,7 @@ server.tool("search_docs", "Search football data provider documentation. Use for
|
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
// Tool: list_providers
|
|
134
|
-
server.tool("list_providers", "List all indexed football data providers, their document count, and coverage categories. Use to understand what documentation is available.", {}, async () => {
|
|
134
|
+
server.tool("list_providers", "List all indexed football data providers, their document count, and coverage categories. Use to understand what documentation is available. Call this first to see what providers are indexed before searching.", {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
|
|
135
135
|
const db = openDb();
|
|
136
136
|
try {
|
|
137
137
|
const rows = db
|
|
@@ -167,7 +167,7 @@ server.tool("list_providers", "List all indexed football data providers, their d
|
|
|
167
167
|
server.tool("compare_providers", "Compare what two or more providers offer for a specific data type or concept. For example: 'How do Opta and StatsBomb represent shot events differently?'", {
|
|
168
168
|
topic: z.string().describe("The concept to compare across providers. Examples: 'shot events', 'coordinate systems', 'xG', 'pass types'"),
|
|
169
169
|
providers: z.array(z.string()).optional().describe("Providers to compare. If omitted, compares all indexed providers."),
|
|
170
|
-
}, async ({ topic, providers }) => {
|
|
170
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async ({ topic, providers }) => {
|
|
171
171
|
const db = openDb();
|
|
172
172
|
try {
|
|
173
173
|
const safeTopic = sanitiseFtsQuery(topic);
|
|
@@ -225,7 +225,7 @@ server.tool("request_update", "Request that a provider's documentation be added,
|
|
|
225
225
|
provider: z.string().max(100).describe("Provider name (existing or proposed). Examples: 'statsbomb', 'mplsoccer', 'floodlight'"),
|
|
226
226
|
reason: z.string().max(2000).describe("Why this update is needed. Be specific: version bump, missing event types, new API endpoints, etc."),
|
|
227
227
|
suggested_urls: z.array(z.string().url().max(500)).max(10).optional().describe("URLs for documentation sources (readthedocs, GitHub, llms.txt, etc.)"),
|
|
228
|
-
}, async ({ type, provider, reason, suggested_urls }) => {
|
|
228
|
+
}, { readOnlyHint: false, destructiveHint: false, openWorldHint: false }, async ({ type, provider, reason, suggested_urls }) => {
|
|
229
229
|
const qdb = openQueueDb();
|
|
230
230
|
try {
|
|
231
231
|
// Hard cap on queue size
|
|
@@ -279,6 +279,79 @@ server.tool("request_update", "Request that a provider's documentation be added,
|
|
|
279
279
|
qdb.close();
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
|
+
// Tool: resolve_entity
|
|
283
|
+
const REEP_API = "https://reep-api.rahulkeerthi2-95d.workers.dev";
|
|
284
|
+
server.tool("resolve_entity", "Resolve a football entity (player, team, or coach) to get cross-provider IDs. Use when you need to map between Transfermarkt, FBref, Sofascore, Opta, and other provider IDs, or when you need to look up a player/team/coach by name.", {
|
|
285
|
+
name: z.string().optional().describe("Entity name to search for (e.g. 'Cole Palmer', 'Arsenal'). Fuzzy match on name and aliases."),
|
|
286
|
+
provider: z.string().optional().describe("Source provider for ID resolution (e.g. 'transfermarkt', 'fbref', 'sofascore', 'opta', 'soccerway')"),
|
|
287
|
+
id: z.string().optional().describe("ID from the source provider to resolve to all other IDs"),
|
|
288
|
+
qid: z.string().optional().describe("Wikidata QID for direct lookup (e.g. 'Q99760796')"),
|
|
289
|
+
type: z.enum(["player", "team", "coach"]).optional().describe("Filter results by entity type"),
|
|
290
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ name, provider, id, qid, type }) => {
|
|
291
|
+
let url;
|
|
292
|
+
if (qid) {
|
|
293
|
+
url = `${REEP_API}/lookup?qid=${encodeURIComponent(qid)}`;
|
|
294
|
+
}
|
|
295
|
+
else if (provider && id) {
|
|
296
|
+
url = `${REEP_API}/resolve?provider=${encodeURIComponent(provider)}&id=${encodeURIComponent(id)}`;
|
|
297
|
+
}
|
|
298
|
+
else if (name) {
|
|
299
|
+
url = `${REEP_API}/search?name=${encodeURIComponent(name)}&limit=10`;
|
|
300
|
+
if (type)
|
|
301
|
+
url += `&type=${encodeURIComponent(type)}`;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
return {
|
|
305
|
+
isError: true,
|
|
306
|
+
content: [{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: "Provide at least one of: name, qid, or provider+id. Examples:\n- name: 'Cole Palmer'\n- provider: 'transfermarkt', id: '568177'\n- qid: 'Q99760796'",
|
|
309
|
+
}],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const resp = await fetch(url);
|
|
314
|
+
if (!resp.ok) {
|
|
315
|
+
return {
|
|
316
|
+
isError: true,
|
|
317
|
+
content: [{ type: "text", text: `Reep API error: ${resp.status} ${resp.statusText}` }],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const data = await resp.json();
|
|
321
|
+
if (!data.results?.length) {
|
|
322
|
+
return {
|
|
323
|
+
content: [{ type: "text", text: "No entities found matching the query." }],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const formatted = data.results.map((e) => {
|
|
327
|
+
const ids = e.external_ids;
|
|
328
|
+
const idLines = ids
|
|
329
|
+
? Object.entries(ids).map(([k, v]) => ` ${k}: ${v}`).join("\n")
|
|
330
|
+
: " (none)";
|
|
331
|
+
const bio = [
|
|
332
|
+
e.date_of_birth && `DOB: ${e.date_of_birth}`,
|
|
333
|
+
e.nationality && `Nationality: ${e.nationality}`,
|
|
334
|
+
e.position && `Position: ${e.position}`,
|
|
335
|
+
e.height_cm && `Height: ${e.height_cm}cm`,
|
|
336
|
+
e.country && `Country: ${e.country}`,
|
|
337
|
+
e.stadium && `Stadium: ${e.stadium}`,
|
|
338
|
+
].filter(Boolean).join(" | ");
|
|
339
|
+
return `### ${e.name_en} (${e.type})\nWikidata: ${e.qid}${e.aliases_en ? `\nAliases: ${e.aliases_en}` : ""}${bio ? `\n${bio}` : ""}\nProvider IDs:\n${idLines}`;
|
|
340
|
+
}).join("\n\n");
|
|
341
|
+
return {
|
|
342
|
+
content: [{
|
|
343
|
+
type: "text",
|
|
344
|
+
text: `Found ${data.results.length} result(s):\n\n${formatted}`,
|
|
345
|
+
}],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
return {
|
|
350
|
+
isError: true,
|
|
351
|
+
content: [{ type: "text", text: `Failed to reach Reep API: ${err instanceof Error ? err.message : String(err)}` }],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
});
|
|
282
355
|
// ── Start ───────────────────────────────────────────────────────────────
|
|
283
356
|
async function main() {
|
|
284
357
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "football-docs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Searchable football data provider documentation for AI coding agents. Like Context7 for football data.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"mcpName": "io.github.withqwerty/football-docs",
|
|
6
7
|
"bin": {
|
|
7
8
|
"football-docs": "bin/serve.js"
|
|
8
9
|
},
|