@yawlabs/tokenmeter-mcp 0.1.0 → 0.2.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/dist/index.js +67 -89
- package/package.json +1 -1
- package/src/index.ts +81 -232
package/dist/index.js
CHANGED
|
@@ -37,100 +37,78 @@ async function apiCall(endpoint, params = {}) {
|
|
|
37
37
|
function text(data) {
|
|
38
38
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
39
39
|
}
|
|
40
|
+
const ALL_TOOLS = [
|
|
41
|
+
// Free
|
|
42
|
+
{ name: "get_spend_summary", description: "Get total LLM spend for today, this week, and this month, broken down by provider.", schema: {}, tier: "free" },
|
|
43
|
+
{ name: "get_session_cost", description: "Get the cost of the current conversation/session.", schema: {}, tier: "free" },
|
|
44
|
+
{ name: "get_model_pricing", description: "Get current per-token pricing for any model or provider.", schema: { model: z.string().optional().describe("Model ID"), provider: z.string().optional().describe("Provider name") }, tier: "free" },
|
|
45
|
+
{ name: "get_budget_status", description: "Check current spend against configured budget limits.", schema: {}, tier: "free" },
|
|
46
|
+
{ name: "get_providers", description: "List connected LLM providers and their status.", schema: {}, tier: "free" },
|
|
47
|
+
// Pro
|
|
48
|
+
{ name: "get_spend_breakdown", description: "Detailed spend breakdown with filters by provider, model, and date range.", schema: { provider: z.string().optional(), days: z.number().optional().default(7) }, tier: "pro" },
|
|
49
|
+
{ name: "get_cost_trend", description: "Show daily spend over time to identify trends.", schema: { days: z.number().optional().default(14) }, tier: "pro" },
|
|
50
|
+
{ name: "get_top_models", description: "Rank models by spend or token volume.", schema: { sortBy: z.enum(["cost", "tokens"]).optional().default("cost"), days: z.number().optional().default(30) }, tier: "pro" },
|
|
51
|
+
{ name: "get_cost_estimate", description: "Estimate cost for a given number of tokens on specified models.", schema: { models: z.array(z.string()), inputTokens: z.number(), outputTokens: z.number().optional().default(500), useCache: z.boolean().optional().default(false) }, tier: "pro" },
|
|
52
|
+
{ name: "compare_models", description: "Compare models on cost for a specific workload.", schema: { inputTokens: z.number(), outputTokens: z.number(), callsPerDay: z.number().optional().default(100), provider: z.string().optional() }, tier: "pro" },
|
|
53
|
+
{ name: "get_anomalies", description: "Flag unusual spend spikes compared to historical average.", schema: {}, tier: "pro" },
|
|
54
|
+
{ name: "set_budget_alert", description: "Create a spend threshold notification or hard cap.", schema: { name: z.string(), limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]), hardCap: z.boolean().optional().default(false) }, tier: "pro" },
|
|
55
|
+
{ name: "set_budget_cap", description: "Set a hard spending cap that blocks requests when hit.", schema: { limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "pro" },
|
|
56
|
+
{ name: "tag_session", description: "Label the current session with a project name for cost attribution.", schema: { project: z.string() }, tier: "pro" },
|
|
57
|
+
{ name: "export_report", description: "Generate a spend report for a date range.", schema: { days: z.number().optional().default(30), format: z.enum(["json", "csv"]).optional().default("json") }, tier: "pro" },
|
|
58
|
+
// Gateway
|
|
59
|
+
{ name: "get_rate_limits", description: "Show current rate limit status per provider and model.", schema: {}, tier: "gateway" },
|
|
60
|
+
{ name: "get_routing_status", description: "Show which providers are healthy, degraded, or rate-limited.", schema: {}, tier: "gateway" },
|
|
61
|
+
{ name: "set_routing_rule", description: "Configure routing preferences (cheapest, fastest, specific provider).", schema: { name: z.string(), strategy: z.enum(["cheapest", "fastest", "specific"]), provider: z.string().optional(), model: z.string().optional() }, tier: "gateway" },
|
|
62
|
+
{ name: "set_fallback_chain", description: "Define provider failover order.", schema: { chain: z.array(z.object({ provider: z.string(), model: z.string() })) }, tier: "gateway" },
|
|
63
|
+
{ name: "set_project_quota", description: "Set a hard spend cap per project.", schema: { project: z.string(), limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "gateway" },
|
|
64
|
+
{ name: "get_latency_report", description: "Show P50/P95/P99 latency per provider and model.", schema: { days: z.number().optional().default(7) }, tier: "gateway" },
|
|
65
|
+
{ name: "set_model_alias", description: "Map a friendly name to a routing rule.", schema: { alias: z.string(), provider: z.string(), model: z.string() }, tier: "gateway" },
|
|
66
|
+
// Team
|
|
67
|
+
{ name: "get_team_spend", description: "Get aggregated spend across all team members.", schema: { days: z.number().optional().default(30) }, tier: "team" },
|
|
68
|
+
{ name: "set_team_budget", description: "Set an organization-level budget cap.", schema: { limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "team" },
|
|
69
|
+
];
|
|
70
|
+
// ─── Main ───────────────────────────────────────────────
|
|
40
71
|
async function main() {
|
|
41
72
|
if (!API_KEY) {
|
|
42
73
|
console.error("TOKENMETER_API_KEY is required. Get your key at https://tokenmeter.sh/settings");
|
|
43
74
|
process.exit(1);
|
|
44
75
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
name: z.string(),
|
|
82
|
-
limit: z.number(),
|
|
83
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
84
|
-
hardCap: z.boolean().optional().default(false),
|
|
85
|
-
}, async (params) => text(await apiCall("/v1/tools/set_budget_alert", params)));
|
|
86
|
-
server.tool("set_budget_cap", "Set a hard spending cap that blocks requests when hit.", {
|
|
87
|
-
limit: z.number(),
|
|
88
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
89
|
-
}, async (params) => text(await apiCall("/v1/tools/set_budget_cap", params)));
|
|
90
|
-
server.tool("tag_session", "Label the current session with a project name for cost attribution.", {
|
|
91
|
-
project: z.string(),
|
|
92
|
-
}, async (params) => text(await apiCall("/v1/tools/tag_session", params)));
|
|
93
|
-
server.tool("export_report", "Generate a spend report for a date range.", {
|
|
94
|
-
days: z.number().optional().default(30),
|
|
95
|
-
format: z.enum(["json", "csv"]).optional().default("json"),
|
|
96
|
-
}, async (params) => text(await apiCall("/v1/tools/export_report", params)));
|
|
97
|
-
// ─── Gateway Tier ────────────────────────────────────────
|
|
98
|
-
server.tool("get_rate_limits", "Show current rate limit status per provider and model.", {}, async () => text(await apiCall("/v1/tools/get_rate_limits")));
|
|
99
|
-
server.tool("get_routing_status", "Show which providers are healthy, degraded, or rate-limited.", {}, async () => text(await apiCall("/v1/tools/get_routing_status")));
|
|
100
|
-
server.tool("set_routing_rule", "Configure routing preferences (cheapest, fastest, specific provider).", {
|
|
101
|
-
name: z.string(),
|
|
102
|
-
strategy: z.enum(["cheapest", "fastest", "specific"]),
|
|
103
|
-
provider: z.string().optional(),
|
|
104
|
-
model: z.string().optional(),
|
|
105
|
-
}, async (params) => text(await apiCall("/v1/tools/set_routing_rule", params)));
|
|
106
|
-
server.tool("set_fallback_chain", "Define provider failover order.", {
|
|
107
|
-
chain: z.array(z.object({
|
|
108
|
-
provider: z.string(),
|
|
109
|
-
model: z.string(),
|
|
110
|
-
})),
|
|
111
|
-
}, async (params) => text(await apiCall("/v1/tools/set_fallback_chain", params)));
|
|
112
|
-
server.tool("set_project_quota", "Set a hard spend cap per project.", {
|
|
113
|
-
project: z.string(),
|
|
114
|
-
limit: z.number(),
|
|
115
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
116
|
-
}, async (params) => text(await apiCall("/v1/tools/set_project_quota", params)));
|
|
117
|
-
server.tool("get_latency_report", "Show P50/P95/P99 latency per provider and model.", {
|
|
118
|
-
days: z.number().optional().default(7),
|
|
119
|
-
}, async (params) => text(await apiCall("/v1/tools/get_latency_report", params)));
|
|
120
|
-
server.tool("set_model_alias", "Map a friendly name to a routing rule.", {
|
|
121
|
-
alias: z.string(),
|
|
122
|
-
provider: z.string(),
|
|
123
|
-
model: z.string(),
|
|
124
|
-
}, async (params) => text(await apiCall("/v1/tools/set_model_alias", params)));
|
|
125
|
-
// ─── Team Tier ───────────────────────────────────────────
|
|
126
|
-
server.tool("get_team_spend", "Get aggregated spend across all team members.", {
|
|
127
|
-
days: z.number().optional().default(30),
|
|
128
|
-
}, async (params) => text(await apiCall("/v1/tools/get_team_spend", params)));
|
|
129
|
-
server.tool("set_team_budget", "Set an organization-level budget cap.", {
|
|
130
|
-
limit: z.number(),
|
|
131
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
132
|
-
}, async (params) => text(await apiCall("/v1/tools/set_team_budget", params)));
|
|
133
|
-
// Connect
|
|
76
|
+
// Fetch user's tier to only register tools they have access to
|
|
77
|
+
let userTier = "free";
|
|
78
|
+
try {
|
|
79
|
+
const me = await apiCall("/v1/auth/me", {});
|
|
80
|
+
// auth/me is a GET endpoint, but the client only does POST — fall back to checking tier from tool response
|
|
81
|
+
if (me?.user?.tier)
|
|
82
|
+
userTier = me.user.tier;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// If we can't determine tier, try fetching via a GET
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`${API_BASE}/v1/auth/me`, {
|
|
88
|
+
headers: { authorization: `Bearer ${API_KEY}` },
|
|
89
|
+
});
|
|
90
|
+
if (res.ok) {
|
|
91
|
+
const data = await res.json();
|
|
92
|
+
if (data?.user?.tier)
|
|
93
|
+
userTier = data.user.tier;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Default to free — server will enforce tier gating anyway
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const tierRank = { free: 0, pro: 1, gateway: 2, team: 3, enterprise: 4 };
|
|
101
|
+
const userRank = tierRank[userTier] ?? 0;
|
|
102
|
+
const server = new McpServer({ name: "tokenmeter", version: "0.2.0" });
|
|
103
|
+
// Register only tools the user's tier has access to
|
|
104
|
+
for (const tool of ALL_TOOLS) {
|
|
105
|
+
const toolRank = tierRank[tool.tier] ?? 0;
|
|
106
|
+
if (userRank < toolRank)
|
|
107
|
+
continue;
|
|
108
|
+
server.tool(tool.name, tool.description, tool.schema, async (params) => text(await apiCall(`/v1/tools/${tool.name}`, params)));
|
|
109
|
+
}
|
|
110
|
+
const toolCount = ALL_TOOLS.filter((t) => userRank >= (tierRank[t.tier] ?? 0)).length;
|
|
111
|
+
console.error(`Token Meter MCP — ${toolCount} tools registered (${userTier} tier)`);
|
|
134
112
|
const transport = new StdioServerTransport();
|
|
135
113
|
await server.connect(transport);
|
|
136
114
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -45,250 +45,99 @@ function text(data: unknown) {
|
|
|
45
45
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// ─── Tool schemas ───────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
interface ToolConfig {
|
|
51
|
+
name: string;
|
|
52
|
+
description: string;
|
|
53
|
+
schema: Record<string, z.ZodType>;
|
|
54
|
+
tier: "free" | "pro" | "gateway" | "team";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ALL_TOOLS: ToolConfig[] = [
|
|
58
|
+
// Free
|
|
59
|
+
{ name: "get_spend_summary", description: "Get total LLM spend for today, this week, and this month, broken down by provider.", schema: {}, tier: "free" },
|
|
60
|
+
{ name: "get_session_cost", description: "Get the cost of the current conversation/session.", schema: {}, tier: "free" },
|
|
61
|
+
{ name: "get_model_pricing", description: "Get current per-token pricing for any model or provider.", schema: { model: z.string().optional().describe("Model ID"), provider: z.string().optional().describe("Provider name") }, tier: "free" },
|
|
62
|
+
{ name: "get_budget_status", description: "Check current spend against configured budget limits.", schema: {}, tier: "free" },
|
|
63
|
+
{ name: "get_providers", description: "List connected LLM providers and their status.", schema: {}, tier: "free" },
|
|
64
|
+
|
|
65
|
+
// Pro
|
|
66
|
+
{ name: "get_spend_breakdown", description: "Detailed spend breakdown with filters by provider, model, and date range.", schema: { provider: z.string().optional(), days: z.number().optional().default(7) }, tier: "pro" },
|
|
67
|
+
{ name: "get_cost_trend", description: "Show daily spend over time to identify trends.", schema: { days: z.number().optional().default(14) }, tier: "pro" },
|
|
68
|
+
{ name: "get_top_models", description: "Rank models by spend or token volume.", schema: { sortBy: z.enum(["cost", "tokens"]).optional().default("cost"), days: z.number().optional().default(30) }, tier: "pro" },
|
|
69
|
+
{ name: "get_cost_estimate", description: "Estimate cost for a given number of tokens on specified models.", schema: { models: z.array(z.string()), inputTokens: z.number(), outputTokens: z.number().optional().default(500), useCache: z.boolean().optional().default(false) }, tier: "pro" },
|
|
70
|
+
{ name: "compare_models", description: "Compare models on cost for a specific workload.", schema: { inputTokens: z.number(), outputTokens: z.number(), callsPerDay: z.number().optional().default(100), provider: z.string().optional() }, tier: "pro" },
|
|
71
|
+
{ name: "get_anomalies", description: "Flag unusual spend spikes compared to historical average.", schema: {}, tier: "pro" },
|
|
72
|
+
{ name: "set_budget_alert", description: "Create a spend threshold notification or hard cap.", schema: { name: z.string(), limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]), hardCap: z.boolean().optional().default(false) }, tier: "pro" },
|
|
73
|
+
{ name: "set_budget_cap", description: "Set a hard spending cap that blocks requests when hit.", schema: { limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "pro" },
|
|
74
|
+
{ name: "tag_session", description: "Label the current session with a project name for cost attribution.", schema: { project: z.string() }, tier: "pro" },
|
|
75
|
+
{ name: "export_report", description: "Generate a spend report for a date range.", schema: { days: z.number().optional().default(30), format: z.enum(["json", "csv"]).optional().default("json") }, tier: "pro" },
|
|
76
|
+
|
|
77
|
+
// Gateway
|
|
78
|
+
{ name: "get_rate_limits", description: "Show current rate limit status per provider and model.", schema: {}, tier: "gateway" },
|
|
79
|
+
{ name: "get_routing_status", description: "Show which providers are healthy, degraded, or rate-limited.", schema: {}, tier: "gateway" },
|
|
80
|
+
{ name: "set_routing_rule", description: "Configure routing preferences (cheapest, fastest, specific provider).", schema: { name: z.string(), strategy: z.enum(["cheapest", "fastest", "specific"]), provider: z.string().optional(), model: z.string().optional() }, tier: "gateway" },
|
|
81
|
+
{ name: "set_fallback_chain", description: "Define provider failover order.", schema: { chain: z.array(z.object({ provider: z.string(), model: z.string() })) }, tier: "gateway" },
|
|
82
|
+
{ name: "set_project_quota", description: "Set a hard spend cap per project.", schema: { project: z.string(), limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "gateway" },
|
|
83
|
+
{ name: "get_latency_report", description: "Show P50/P95/P99 latency per provider and model.", schema: { days: z.number().optional().default(7) }, tier: "gateway" },
|
|
84
|
+
{ name: "set_model_alias", description: "Map a friendly name to a routing rule.", schema: { alias: z.string(), provider: z.string(), model: z.string() }, tier: "gateway" },
|
|
85
|
+
|
|
86
|
+
// Team
|
|
87
|
+
{ name: "get_team_spend", description: "Get aggregated spend across all team members.", schema: { days: z.number().optional().default(30) }, tier: "team" },
|
|
88
|
+
{ name: "set_team_budget", description: "Set an organization-level budget cap.", schema: { limit: z.number(), period: z.enum(["daily", "weekly", "monthly"]) }, tier: "team" },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
// ─── Main ───────────────────────────────────────────────
|
|
92
|
+
|
|
48
93
|
async function main() {
|
|
49
94
|
if (!API_KEY) {
|
|
50
95
|
console.error("TOKENMETER_API_KEY is required. Get your key at https://tokenmeter.sh/settings");
|
|
51
96
|
process.exit(1);
|
|
52
97
|
}
|
|
53
98
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"Get current per-token pricing for any model or provider.",
|
|
75
|
-
{
|
|
76
|
-
model: z.string().optional().describe("Model ID"),
|
|
77
|
-
provider: z.string().optional().describe("Provider name"),
|
|
78
|
-
},
|
|
79
|
-
async (params) => text(await apiCall("/v1/tools/get_model_pricing", params))
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
server.tool(
|
|
83
|
-
"get_budget_status",
|
|
84
|
-
"Check current spend against configured budget limits.",
|
|
85
|
-
{},
|
|
86
|
-
async () => text(await apiCall("/v1/tools/get_budget_status"))
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
server.tool(
|
|
90
|
-
"get_providers",
|
|
91
|
-
"List connected LLM providers and their status.",
|
|
92
|
-
{},
|
|
93
|
-
async () => text(await apiCall("/v1/tools/get_providers"))
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// ─── Pro Tier ────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
server.tool(
|
|
99
|
-
"get_spend_breakdown",
|
|
100
|
-
"Detailed spend breakdown with filters by provider, model, project, and date range.",
|
|
101
|
-
{
|
|
102
|
-
provider: z.string().optional(),
|
|
103
|
-
days: z.number().optional().default(7),
|
|
104
|
-
},
|
|
105
|
-
async (params) => text(await apiCall("/v1/tools/get_spend_breakdown", params))
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
server.tool(
|
|
109
|
-
"get_cost_trend",
|
|
110
|
-
"Show daily spend over time to identify trends.",
|
|
111
|
-
{
|
|
112
|
-
days: z.number().optional().default(14),
|
|
113
|
-
},
|
|
114
|
-
async (params) => text(await apiCall("/v1/tools/get_cost_trend", params))
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
server.tool(
|
|
118
|
-
"get_top_models",
|
|
119
|
-
"Rank models by spend or token volume.",
|
|
120
|
-
{
|
|
121
|
-
sortBy: z.enum(["cost", "tokens"]).optional().default("cost"),
|
|
122
|
-
days: z.number().optional().default(30),
|
|
123
|
-
},
|
|
124
|
-
async (params) => text(await apiCall("/v1/tools/get_top_models", params))
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
server.tool(
|
|
128
|
-
"get_cost_estimate",
|
|
129
|
-
"Estimate cost for a given number of tokens on specified models.",
|
|
130
|
-
{
|
|
131
|
-
models: z.array(z.string()),
|
|
132
|
-
inputTokens: z.number(),
|
|
133
|
-
outputTokens: z.number().optional().default(500),
|
|
134
|
-
useCache: z.boolean().optional().default(false),
|
|
135
|
-
},
|
|
136
|
-
async (params) => text(await apiCall("/v1/tools/get_cost_estimate", params))
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
server.tool(
|
|
140
|
-
"compare_models",
|
|
141
|
-
"Compare models on cost for a specific workload.",
|
|
142
|
-
{
|
|
143
|
-
inputTokens: z.number(),
|
|
144
|
-
outputTokens: z.number(),
|
|
145
|
-
callsPerDay: z.number().optional().default(100),
|
|
146
|
-
provider: z.string().optional(),
|
|
147
|
-
},
|
|
148
|
-
async (params) => text(await apiCall("/v1/tools/compare_models", params))
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
server.tool(
|
|
152
|
-
"get_anomalies",
|
|
153
|
-
"Flag unusual spend spikes compared to historical average.",
|
|
154
|
-
{},
|
|
155
|
-
async () => text(await apiCall("/v1/tools/get_anomalies"))
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
server.tool(
|
|
159
|
-
"set_budget_alert",
|
|
160
|
-
"Create a spend threshold notification or hard cap.",
|
|
161
|
-
{
|
|
162
|
-
name: z.string(),
|
|
163
|
-
limit: z.number(),
|
|
164
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
165
|
-
hardCap: z.boolean().optional().default(false),
|
|
166
|
-
},
|
|
167
|
-
async (params) => text(await apiCall("/v1/tools/set_budget_alert", params))
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
server.tool(
|
|
171
|
-
"set_budget_cap",
|
|
172
|
-
"Set a hard spending cap that blocks requests when hit.",
|
|
173
|
-
{
|
|
174
|
-
limit: z.number(),
|
|
175
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
176
|
-
},
|
|
177
|
-
async (params) => text(await apiCall("/v1/tools/set_budget_cap", params))
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
server.tool(
|
|
181
|
-
"tag_session",
|
|
182
|
-
"Label the current session with a project name for cost attribution.",
|
|
183
|
-
{
|
|
184
|
-
project: z.string(),
|
|
185
|
-
},
|
|
186
|
-
async (params) => text(await apiCall("/v1/tools/tag_session", params))
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
server.tool(
|
|
190
|
-
"export_report",
|
|
191
|
-
"Generate a spend report for a date range.",
|
|
192
|
-
{
|
|
193
|
-
days: z.number().optional().default(30),
|
|
194
|
-
format: z.enum(["json", "csv"]).optional().default("json"),
|
|
195
|
-
},
|
|
196
|
-
async (params) => text(await apiCall("/v1/tools/export_report", params))
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// ─── Gateway Tier ────────────────────────────────────────
|
|
200
|
-
|
|
201
|
-
server.tool(
|
|
202
|
-
"get_rate_limits",
|
|
203
|
-
"Show current rate limit status per provider and model.",
|
|
204
|
-
{},
|
|
205
|
-
async () => text(await apiCall("/v1/tools/get_rate_limits"))
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
server.tool(
|
|
209
|
-
"get_routing_status",
|
|
210
|
-
"Show which providers are healthy, degraded, or rate-limited.",
|
|
211
|
-
{},
|
|
212
|
-
async () => text(await apiCall("/v1/tools/get_routing_status"))
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
server.tool(
|
|
216
|
-
"set_routing_rule",
|
|
217
|
-
"Configure routing preferences (cheapest, fastest, specific provider).",
|
|
218
|
-
{
|
|
219
|
-
name: z.string(),
|
|
220
|
-
strategy: z.enum(["cheapest", "fastest", "specific"]),
|
|
221
|
-
provider: z.string().optional(),
|
|
222
|
-
model: z.string().optional(),
|
|
223
|
-
},
|
|
224
|
-
async (params) => text(await apiCall("/v1/tools/set_routing_rule", params))
|
|
225
|
-
);
|
|
99
|
+
// Fetch user's tier to only register tools they have access to
|
|
100
|
+
let userTier = "free";
|
|
101
|
+
try {
|
|
102
|
+
const me = await apiCall("/v1/auth/me", {}) as any;
|
|
103
|
+
// auth/me is a GET endpoint, but the client only does POST — fall back to checking tier from tool response
|
|
104
|
+
if (me?.user?.tier) userTier = me.user.tier;
|
|
105
|
+
} catch {
|
|
106
|
+
// If we can't determine tier, try fetching via a GET
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch(`${API_BASE}/v1/auth/me`, {
|
|
109
|
+
headers: { authorization: `Bearer ${API_KEY}` },
|
|
110
|
+
});
|
|
111
|
+
if (res.ok) {
|
|
112
|
+
const data = await res.json() as any;
|
|
113
|
+
if (data?.user?.tier) userTier = data.user.tier;
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// Default to free — server will enforce tier gating anyway
|
|
117
|
+
}
|
|
118
|
+
}
|
|
226
119
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"Define provider failover order.",
|
|
230
|
-
{
|
|
231
|
-
chain: z.array(z.object({
|
|
232
|
-
provider: z.string(),
|
|
233
|
-
model: z.string(),
|
|
234
|
-
})),
|
|
235
|
-
},
|
|
236
|
-
async (params) => text(await apiCall("/v1/tools/set_fallback_chain", params))
|
|
237
|
-
);
|
|
120
|
+
const tierRank: Record<string, number> = { free: 0, pro: 1, gateway: 2, team: 3, enterprise: 4 };
|
|
121
|
+
const userRank = tierRank[userTier] ?? 0;
|
|
238
122
|
|
|
239
|
-
server.
|
|
240
|
-
"set_project_quota",
|
|
241
|
-
"Set a hard spend cap per project.",
|
|
242
|
-
{
|
|
243
|
-
project: z.string(),
|
|
244
|
-
limit: z.number(),
|
|
245
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
246
|
-
},
|
|
247
|
-
async (params) => text(await apiCall("/v1/tools/set_project_quota", params))
|
|
248
|
-
);
|
|
123
|
+
const server = new McpServer({ name: "tokenmeter", version: "0.2.0" });
|
|
249
124
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
days: z.number().optional().default(7),
|
|
255
|
-
},
|
|
256
|
-
async (params) => text(await apiCall("/v1/tools/get_latency_report", params))
|
|
257
|
-
);
|
|
125
|
+
// Register only tools the user's tier has access to
|
|
126
|
+
for (const tool of ALL_TOOLS) {
|
|
127
|
+
const toolRank = tierRank[tool.tier] ?? 0;
|
|
128
|
+
if (userRank < toolRank) continue;
|
|
258
129
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
},
|
|
267
|
-
async (params) => text(await apiCall("/v1/tools/set_model_alias", params))
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
// ─── Team Tier ───────────────────────────────────────────
|
|
271
|
-
|
|
272
|
-
server.tool(
|
|
273
|
-
"get_team_spend",
|
|
274
|
-
"Get aggregated spend across all team members.",
|
|
275
|
-
{
|
|
276
|
-
days: z.number().optional().default(30),
|
|
277
|
-
},
|
|
278
|
-
async (params) => text(await apiCall("/v1/tools/get_team_spend", params))
|
|
279
|
-
);
|
|
130
|
+
server.tool(
|
|
131
|
+
tool.name,
|
|
132
|
+
tool.description,
|
|
133
|
+
tool.schema,
|
|
134
|
+
async (params) => text(await apiCall(`/v1/tools/${tool.name}`, params))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
280
137
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"Set an organization-level budget cap.",
|
|
284
|
-
{
|
|
285
|
-
limit: z.number(),
|
|
286
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
287
|
-
},
|
|
288
|
-
async (params) => text(await apiCall("/v1/tools/set_team_budget", params))
|
|
289
|
-
);
|
|
138
|
+
const toolCount = ALL_TOOLS.filter((t) => userRank >= (tierRank[t.tier] ?? 0)).length;
|
|
139
|
+
console.error(`Token Meter MCP — ${toolCount} tools registered (${userTier} tier)`);
|
|
290
140
|
|
|
291
|
-
// Connect
|
|
292
141
|
const transport = new StdioServerTransport();
|
|
293
142
|
await server.connect(transport);
|
|
294
143
|
}
|