cito-mcp 0.5.2 → 0.5.4
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 +620 -609
- package/dist/client.js +107 -31
- package/dist/envelope.js +79 -1
- package/dist/instructions.js +82 -81
- package/dist/pacing.js +195 -0
- package/dist/tools/meta.js +41 -10
- package/dist/tools/types.js +13 -4
- package/package.json +1 -1
package/dist/tools/meta.js
CHANGED
|
@@ -476,7 +476,7 @@ const TOOL_CATALOG = [
|
|
|
476
476
|
name: 'list_routes',
|
|
477
477
|
outcome: 'Index of raw REST routes from the live OpenAPI spec (method, path, summary)',
|
|
478
478
|
parallelSafe: true,
|
|
479
|
-
games: [...PRIMARY_GAMES, 'fortnite'],
|
|
479
|
+
games: [...PRIMARY_GAMES, 'fortnite', 'apex'],
|
|
480
480
|
jobs: ['app_scaffold'],
|
|
481
481
|
exampleArgs: { game: 'ufc', q: 'rankings' },
|
|
482
482
|
preferOver: ['guessing a REST path for call_api', 'fetching the full 90KB spec by hand'],
|
|
@@ -486,7 +486,7 @@ const TOOL_CATALOG = [
|
|
|
486
486
|
name: 'call_api',
|
|
487
487
|
outcome: 'Allowlisted raw REST escape hatch (unshaped data.raw)',
|
|
488
488
|
parallelSafe: true,
|
|
489
|
-
games: [...PRIMARY_GAMES, 'fortnite'],
|
|
489
|
+
games: [...PRIMARY_GAMES, 'fortnite', 'apex'],
|
|
490
490
|
jobs: ['app_scaffold'],
|
|
491
491
|
exampleArgs: { method: 'GET', path: '/cs2/rankings/teams', queryJson: '{"limit":20}' },
|
|
492
492
|
preferOver: [],
|
|
@@ -558,6 +558,14 @@ const GAMES_META = [
|
|
|
558
558
|
{ id: 'cs2', label: 'Counter-Strike 2', depth: 'deep', notes: 'Prefixed ids; live path /cs2/live; trends/rankings' },
|
|
559
559
|
{ id: 'dota2', label: 'Dota 2', depth: 'thin', notes: 'Numeric ids; radar; no standings/roster endpoints' },
|
|
560
560
|
{ id: 'cod', label: 'Call of Duty (CDL)', depth: 'medium', notes: 'Org slugs; CDL standings; UUID match ids' },
|
|
561
|
+
{
|
|
562
|
+
id: 'apex',
|
|
563
|
+
label: 'Apex Legends (ALGS)',
|
|
564
|
+
depth: 'thin',
|
|
565
|
+
notes: 'call_api only (no curated tools): /apex/algs/events, /apex/algs/events/{seriesId} (+ /scores, /bans), ' +
|
|
566
|
+
'/apex/algs/matches, /apex/algs/players (+ /search), /apex/algs/global-stats/{eventId} (+ /players, /legends), ' +
|
|
567
|
+
'/apex/algs/transactions, /apex/algs/regions, /apex/algs/maps. Not in the OpenAPI spec yet (list_routes { game: "apex" } returns this path list as a note).',
|
|
568
|
+
},
|
|
561
569
|
{
|
|
562
570
|
id: 'ufc',
|
|
563
571
|
label: 'UFC / MMA',
|
|
@@ -776,20 +784,38 @@ Example: { "includeGameProbes": true }`,
|
|
|
776
784
|
else if (keyValid) {
|
|
777
785
|
includedGames.push(...PRIMARY_GAMES);
|
|
778
786
|
}
|
|
787
|
+
const healthBody = health.ok && health.data && typeof health.data === 'object' && !Array.isArray(health.data)
|
|
788
|
+
? health.data
|
|
789
|
+
: null;
|
|
790
|
+
const limits = healthBody?.rateLimit && typeof healthBody.rateLimit === 'object'
|
|
791
|
+
? healthBody.rateLimit
|
|
792
|
+
: null;
|
|
793
|
+
const perMinute = limits?.perMinute && typeof limits.perMinute === 'object'
|
|
794
|
+
? limits.perMinute
|
|
795
|
+
: null;
|
|
796
|
+
const headerRate = rateLimit;
|
|
797
|
+
const tier = headerRate.tier ?? (typeof healthBody?.tier === 'string' ? healthBody.tier : null);
|
|
798
|
+
const mergedRateLimit = {
|
|
799
|
+
...headerRate,
|
|
800
|
+
tier,
|
|
801
|
+
limit: headerRate.limit ?? (typeof perMinute?.limit === 'number' ? perMinute.limit : null),
|
|
802
|
+
};
|
|
779
803
|
return successEnvelope({
|
|
780
804
|
source: 'api_health',
|
|
781
805
|
game: null,
|
|
782
806
|
requestId,
|
|
783
807
|
tookMs: Date.now() - started,
|
|
784
808
|
upstreamCalls,
|
|
785
|
-
rateLimit,
|
|
809
|
+
rateLimit: mergedRateLimit,
|
|
786
810
|
data: {
|
|
787
811
|
reachable,
|
|
788
812
|
keyValid,
|
|
789
813
|
answeredBy,
|
|
790
814
|
httpStatus: authedStatus,
|
|
791
|
-
tier
|
|
792
|
-
|
|
815
|
+
tier,
|
|
816
|
+
plan: typeof healthBody?.plan === 'string' ? healthBody.plan : null,
|
|
817
|
+
rateLimit: mergedRateLimit,
|
|
818
|
+
...(limits ? { limits } : {}),
|
|
793
819
|
includedGames,
|
|
794
820
|
...(probes.length ? { probes } : {}),
|
|
795
821
|
},
|
|
@@ -804,6 +830,7 @@ const ALLOWLIST_PREFIXES = [
|
|
|
804
830
|
'/cod',
|
|
805
831
|
'/ufc',
|
|
806
832
|
'/fortnite',
|
|
833
|
+
'/apex',
|
|
807
834
|
'/tennis',
|
|
808
835
|
'/raf',
|
|
809
836
|
'/bkfc',
|
|
@@ -840,7 +867,7 @@ Prefer curated tools for all standard jobs (live, schedule, profiles, standings,
|
|
|
840
867
|
|
|
841
868
|
Do not use when: a curated tool covers the outcome. Avoid parallel storms; same plan rate limits apply.
|
|
842
869
|
|
|
843
|
-
Path must start with / and match allowlisted prefixes: /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma.
|
|
870
|
+
Path must start with / and match allowlisted prefixes: /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /apex, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma.
|
|
844
871
|
Rejects absolute URLs and path traversal → PATH_NOT_ALLOWED.
|
|
845
872
|
|
|
846
873
|
Parallel-safe: yes but discouraged in bulk. Upstream cost: 1.
|
|
@@ -901,7 +928,7 @@ Example: { "method": "GET", "path": "/cs2/rankings/teams", "queryJson": "{\\"pag
|
|
|
901
928
|
source: 'call_api',
|
|
902
929
|
requestId,
|
|
903
930
|
tookMs: Date.now() - started,
|
|
904
|
-
hint: 'Use prefixes /health /lol /cs2 /dota2 /cod /ufc /fortnite /tennis /raf /bkfc /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
|
|
931
|
+
hint: 'Use prefixes /health /lol /cs2 /dota2 /cod /ufc /fortnite /apex /tennis /raf /bkfc /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
|
|
905
932
|
});
|
|
906
933
|
}
|
|
907
934
|
let query = {};
|
|
@@ -962,6 +989,10 @@ Example: { "method": "GET", "path": "/cs2/rankings/teams", "queryJson": "{\\"pag
|
|
|
962
989
|
const SPEC_OMITS = {
|
|
963
990
|
tennis: 'The published OpenAPI spec documents no /tennis paths, but tennis routes exist and work (players, matches, h2h, rankings, tournaments, live). Use the curated tools with game tennis, or call_api with /tennis/* paths.',
|
|
964
991
|
lol: 'The published OpenAPI spec documents no /lol paths, but LoL routes exist and work. Use the curated LoL tools (live_matches, upcoming_schedule, team_profile, standings, player_profile); for raw access, /lol/* is allowlisted for call_api even though it is undocumented.',
|
|
992
|
+
apex: 'The published OpenAPI spec documents no /apex paths, but the Apex Legends ALGS routes exist and work through call_api: '
|
|
993
|
+
+ 'GET /apex/algs/events, /apex/algs/events/{seriesId} (+ /scores, /bans), /apex/algs/matches, /apex/algs/players (+ /search?q=), '
|
|
994
|
+
+ '/apex/algs/global-stats (+ /{eventId}, /{eventId}/players, /{eventId}/legends), /apex/algs/transactions, /apex/algs/tournaments, '
|
|
995
|
+
+ '/apex/algs/regions, /apex/algs/maps.',
|
|
965
996
|
};
|
|
966
997
|
export const listRoutes = {
|
|
967
998
|
name: 'list_routes',
|
|
@@ -1002,10 +1033,10 @@ Example: { "game": "ufc", "q": "rankings" }`,
|
|
|
1002
1033
|
const started = Date.now();
|
|
1003
1034
|
const requestId = newRequestId();
|
|
1004
1035
|
const gameRaw = typeof args.game === 'string' ? args.game.toLowerCase().trim() : '';
|
|
1005
|
-
if (gameRaw && !gameRaw.match(/^(lol|cs2|dota2|cod|ufc|fortnite|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma)$/)) {
|
|
1036
|
+
if (gameRaw && !gameRaw.match(/^(lol|cs2|dota2|cod|ufc|fortnite|apex|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma)$/)) {
|
|
1006
1037
|
return errorEnvelope({
|
|
1007
1038
|
code: 'UNSUPPORTED_GAME',
|
|
1008
|
-
message: `unsupported game "${gameRaw}"; use lol|cs2|dota2|cod|ufc|fortnite|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma`,
|
|
1039
|
+
message: `unsupported game "${gameRaw}"; use lol|cs2|dota2|cod|ufc|fortnite|apex|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma`,
|
|
1009
1040
|
game: null,
|
|
1010
1041
|
source: 'list_routes',
|
|
1011
1042
|
requestId,
|
|
@@ -1028,7 +1059,7 @@ Example: { "game": "ufc", "q": "rankings" }`,
|
|
|
1028
1059
|
httpStatus: res.status,
|
|
1029
1060
|
recover: [
|
|
1030
1061
|
'Call list_capabilities for curated tools that need no route knowledge',
|
|
1031
|
-
'call_api accepts /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma',
|
|
1062
|
+
'call_api accepts /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /apex, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma',
|
|
1032
1063
|
],
|
|
1033
1064
|
});
|
|
1034
1065
|
}
|
package/dist/tools/types.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ENVELOPE_OUTPUT_SCHEMA, toMcpResult } from '../envelope.js';
|
|
1
|
+
import { ENVELOPE_OUTPUT_SCHEMA, applyRateLimitContext, toMcpResult } from '../envelope.js';
|
|
2
|
+
import { newCallBudget } from '../pacing.js';
|
|
2
3
|
export const READ_TOOL_ANNOTATIONS = {
|
|
3
4
|
readOnlyHint: true,
|
|
4
5
|
destructiveHint: false,
|
|
@@ -138,11 +139,19 @@ export async function runTool(def, args, ctx) {
|
|
|
138
139
|
],
|
|
139
140
|
}));
|
|
140
141
|
}
|
|
141
|
-
const
|
|
142
|
+
const budget = ctx.budget ?? newCallBudget();
|
|
143
|
+
const result = await def.handler(args ?? {}, { ...ctx, budget });
|
|
142
144
|
if (result && typeof result === 'object' && 'content' in result) {
|
|
143
|
-
|
|
145
|
+
const mcp = result;
|
|
146
|
+
const structured = mcp.structuredContent;
|
|
147
|
+
if (budget.rateLimit && structured && typeof structured === 'object' && 'ok' in structured) {
|
|
148
|
+
const enriched = applyRateLimitContext(structured, budget.rateLimit, budget.retries);
|
|
149
|
+
if (enriched !== structured)
|
|
150
|
+
return toMcpResult(enriched);
|
|
151
|
+
}
|
|
152
|
+
return mcp;
|
|
144
153
|
}
|
|
145
|
-
return toMcpResult(result);
|
|
154
|
+
return toMcpResult(applyRateLimitContext(result, budget.rateLimit, budget.retries));
|
|
146
155
|
}
|
|
147
156
|
catch (error) {
|
|
148
157
|
const { errorEnvelope, toMcpResult: toResult } = await import('../envelope.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cito-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Standalone MCP server for the Cito esports and sports API — curated outcome tools for agents (live scoreboards, round economy, opening duels, clutches, vetoes, rosters, tennis, mma).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://cito.gg",
|