@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.
Files changed (3) hide show
  1. package/dist/index.js +67 -89
  2. package/package.json +1 -1
  3. 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
- const server = new McpServer({ name: "tokenmeter", version: "0.1.0" });
46
- // ─── Free Tier ───────────────────────────────────────────
47
- server.tool("get_spend_summary", "Get total LLM spend for today, this week, and this month, broken down by provider.", {}, async () => text(await apiCall("/v1/tools/get_spend_summary")));
48
- server.tool("get_session_cost", "Get the cost of the current conversation/session.", {}, async () => text(await apiCall("/v1/tools/get_session_cost")));
49
- server.tool("get_model_pricing", "Get current per-token pricing for any model or provider.", {
50
- model: z.string().optional().describe("Model ID"),
51
- provider: z.string().optional().describe("Provider name"),
52
- }, async (params) => text(await apiCall("/v1/tools/get_model_pricing", params)));
53
- server.tool("get_budget_status", "Check current spend against configured budget limits.", {}, async () => text(await apiCall("/v1/tools/get_budget_status")));
54
- server.tool("get_providers", "List connected LLM providers and their status.", {}, async () => text(await apiCall("/v1/tools/get_providers")));
55
- // ─── Pro Tier ────────────────────────────────────────────
56
- server.tool("get_spend_breakdown", "Detailed spend breakdown with filters by provider, model, project, and date range.", {
57
- provider: z.string().optional(),
58
- days: z.number().optional().default(7),
59
- }, async (params) => text(await apiCall("/v1/tools/get_spend_breakdown", params)));
60
- server.tool("get_cost_trend", "Show daily spend over time to identify trends.", {
61
- days: z.number().optional().default(14),
62
- }, async (params) => text(await apiCall("/v1/tools/get_cost_trend", params)));
63
- server.tool("get_top_models", "Rank models by spend or token volume.", {
64
- sortBy: z.enum(["cost", "tokens"]).optional().default("cost"),
65
- days: z.number().optional().default(30),
66
- }, async (params) => text(await apiCall("/v1/tools/get_top_models", params)));
67
- server.tool("get_cost_estimate", "Estimate cost for a given number of tokens on specified models.", {
68
- models: z.array(z.string()),
69
- inputTokens: z.number(),
70
- outputTokens: z.number().optional().default(500),
71
- useCache: z.boolean().optional().default(false),
72
- }, async (params) => text(await apiCall("/v1/tools/get_cost_estimate", params)));
73
- server.tool("compare_models", "Compare models on cost for a specific workload.", {
74
- inputTokens: z.number(),
75
- outputTokens: z.number(),
76
- callsPerDay: z.number().optional().default(100),
77
- provider: z.string().optional(),
78
- }, async (params) => text(await apiCall("/v1/tools/compare_models", params)));
79
- server.tool("get_anomalies", "Flag unusual spend spikes compared to historical average.", {}, async () => text(await apiCall("/v1/tools/get_anomalies")));
80
- server.tool("set_budget_alert", "Create a spend threshold notification or hard cap.", {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/tokenmeter-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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
- const server = new McpServer({ name: "tokenmeter", version: "0.1.0" });
55
-
56
- // ─── Free Tier ───────────────────────────────────────────
57
-
58
- server.tool(
59
- "get_spend_summary",
60
- "Get total LLM spend for today, this week, and this month, broken down by provider.",
61
- {},
62
- async () => text(await apiCall("/v1/tools/get_spend_summary"))
63
- );
64
-
65
- server.tool(
66
- "get_session_cost",
67
- "Get the cost of the current conversation/session.",
68
- {},
69
- async () => text(await apiCall("/v1/tools/get_session_cost"))
70
- );
71
-
72
- server.tool(
73
- "get_model_pricing",
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
- server.tool(
228
- "set_fallback_chain",
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.tool(
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
- server.tool(
251
- "get_latency_report",
252
- "Show P50/P95/P99 latency per provider and model.",
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
- server.tool(
260
- "set_model_alias",
261
- "Map a friendly name to a routing rule.",
262
- {
263
- alias: z.string(),
264
- provider: z.string(),
265
- model: z.string(),
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
- server.tool(
282
- "set_team_budget",
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
  }