@modelstat/mcp 0.0.1 → 0.0.2

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 (4) hide show
  1. package/LICENSE +87 -0
  2. package/README.md +15 -12
  3. package/dist/index.js +3 -3
  4. package/package.json +26 -11
package/LICENSE ADDED
@@ -0,0 +1,87 @@
1
+ Copyright (c) 2026 ModelState Inc
2
+
3
+ Source-Available License
4
+
5
+ 1. Grant of Rights
6
+
7
+ Subject to the terms of this License, you are granted a non-exclusive, worldwide,
8
+ non-transferable, non-sublicensable license to:
9
+
10
+ - View, read, and inspect the source code
11
+ - Build, modify, and run the software
12
+ - Use the software internally, including in production, solely to access or interact
13
+ with ModelState Inc’s hosted services
14
+
15
+ 2. Permitted Use
16
+
17
+ You may use the software as a client, agent, or self-hosted component that connects
18
+ to and depends on ModelState Inc’s cloud or hosted services.
19
+
20
+ 3. Restrictions
21
+
22
+ You may NOT, without explicit prior written permission from ModelState Inc:
23
+
24
+ - Use the software to provide a hosted, managed, or SaaS service to third parties
25
+ - Use the software in any product or service that competes with ModelState Inc
26
+ - Use the software to build or operate an alternative to ModelState Inc’s services
27
+ - Redistribute, sublicense, sell, license, or commercially exploit the software
28
+ - Offer the software (modified or unmodified) as part of a commercial offering
29
+ - Make the software available to third parties as a service
30
+ - Use the software for the benefit of third parties (including multi-tenant or shared environments)
31
+ - Circumvent or attempt to circumvent the limitations of this License
32
+ - Remove or alter any licensing, copyright, or attribution notices
33
+
34
+ 4. Definition of Competing Service
35
+
36
+ “Competing Service” means any product or service that provides substantially similar
37
+ functionality to ModelState Inc’s offerings, including but not limited to:
38
+
39
+ - AI or LLM usage tracking, monitoring, or observability systems
40
+ - Model analytics platforms or dashboards
41
+ - Inference tracking, logging, or telemetry pipelines
42
+ - Evaluation, benchmarking, or quality analysis systems for AI/ML models
43
+ - Cost tracking, performance tracking, or optimization systems for model inference
44
+ - Any system that collects, processes, analyzes, or visualizes usage or behavior of AI or machine learning models, including large language models (LLMs), when offered as a product or service
45
+
46
+ 5. Internal Use
47
+
48
+ Use of the software is permitted only for your internal business or personal use,
49
+ and not for the benefit of third parties.
50
+
51
+ 6. Network Use Restriction
52
+
53
+ You may not use the software to expose APIs, endpoints, dashboards, or services to external
54
+ users except as part of accessing ModelState Inc’s services.
55
+
56
+ 7. Data Extraction Restriction
57
+
58
+ You may not use the software to replicate, extract, reverse engineer, or reconstruct
59
+ ModelState Inc’s service behavior, APIs, data models, or system design for the purpose
60
+ of building, improving, or operating a competing system or service.
61
+
62
+ 8. Ownership
63
+
64
+ All rights, title, and interest in the software remain exclusively with ModelState Inc.
65
+ The software is licensed, not sold.
66
+
67
+ 9. Contributions
68
+
69
+ Unless explicitly agreed otherwise in writing, any contributions submitted to the software
70
+ grant ModelState Inc a perpetual, irrevocable, worldwide, royalty-free license to use,
71
+ modify, and distribute those contributions.
72
+
73
+ 10. Termination
74
+
75
+ This License terminates automatically if you violate any of its terms.
76
+ Upon termination, you must immediately cease all use and delete all copies of the software.
77
+
78
+ 11. Disclaimer of Warranty
79
+
80
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
81
+ EXPRESS OR IMPLIED.
82
+
83
+ 12. Limitation of Liability
84
+
85
+ IN NO EVENT SHALL MODELSTATE INC BE LIABLE FOR ANY CLAIM, DAMAGES,
86
+ OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
87
+ OR ITS USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Ask any MCP-compatible AI tool — Claude Desktop, Claude Code, Cursor, Cline, C
7
7
  - "Show me recent sessions over $5."
8
8
  - "Is my modelstat agent healthy?"
9
9
 
10
- Uses the bearer token [`modelstat connect`](https://modelstat.ai/install) already wrote to `~/.config/modelstat/state.json` — no separate auth.
10
+ Uses the bearer token [`npx modelstat@latest`](https://modelstat.ai/install) already wrote to `~/.config/modelstat/state.json` — no separate auth.
11
11
 
12
12
  ## Install
13
13
 
@@ -78,31 +78,34 @@ mcpServers:
78
78
 
79
79
  ## Tools
80
80
 
81
- All tools are **read-only**. The MCP server never issues mutating calls.
81
+ All tools are **read-only** except `assign_session`.
82
82
 
83
83
  | Tool | Purpose |
84
84
  |---|---|
85
- | `get_spend_summary` | Total $ and tokens for a range, split by tool + model. |
86
- | `get_spend_by_project` | Spend grouped by repo / project. |
87
- | `get_spend_by_tool` | Spend grouped by AI tool. |
88
- | `list_recent_sessions` | Most recent sessions with cost. |
89
- | `get_device_status` | Pairing + last-heartbeat status for this machine. |
85
+ | `usage_overview` | Spend/usage headline: cost, list price, savings, tokens, sessions. |
86
+ | `usage_explore` | The charting workhorse — group/stack by `day`, `hour`, `model`, `tool`, `provider`, `session`; metrics from cost to per-class tokens (`tokens_input`, `tokens_cache_read`, …); filter by providers/models/tools/session_ids. |
87
+ | `list_sessions` | Recent sessions with cost + tokens (cursor-paginated). |
88
+ | `session_detail` | One session's token breakdown + segments (redacted abstracts, tags). |
89
+ | `sessions_usage` | Combined usage for an explicit session-id set e.g. one Claude Code conversation across all its compactions/resumes. |
90
+ | `assign_session` | MUTATING: reassign a session's owner. |
90
91
 
91
- `range` accepts: `today`, `7d`, `30d`, `90d`, `mtd`, `ytd`.
92
+ `range` accepts: `today`, `7d`, `30d`, `90d`, `mtd`, `ytd` — or pass explicit RFC3339 `from`/`to`. Omit both for all-time.
93
+
94
+ Prefer remote? The same tools are served over streamable HTTP at `https://modelstat.ai/mcp` — auth with `Authorization: Bearer $(npx -y modelstat@latest token)`. Claude Code users: the [modelstat plugin](https://modelstat.ai/dashboard/mcp) bundles this server and adds the `/stat` charts command.
92
95
 
93
96
  Your MCP client may see additional tools beyond the ones listed above — the live catalog comes from the modelstat backend, and we add new query tools server-side. Ask your client to list available tools to see what's actually exposed for your account.
94
97
 
95
98
  ## Auth & privacy
96
99
 
97
- The MCP server reads the bearer token that `modelstat connect` stored locally. It never transmits that token anywhere except directly to the modelstat API (default `https://modelstat.ai`). Prompts, responses, and file contents never touch this process.
100
+ The MCP server reads the bearer token that `npx modelstat@latest` stored locally. It never transmits that token anywhere except directly to the modelstat API (default `https://modelstat.ai`). Prompts, responses, and file contents never touch this process.
98
101
 
99
102
  Override the API endpoint with `MODELSTAT_API_URL` (for self-hosted / dev). Override the state dir with `MODELSTAT_STATE_DIR`.
100
103
 
101
104
  ## Troubleshooting
102
105
 
103
- - **`modelstat is not paired on this machine`** — run `curl -fsSL https://install.modelstat.ai | sh` first.
104
- - **401 responses** — the bearer expired. Re-run `modelstat connect`.
105
- - **No data yet** — the agent uploads within a few seconds of your first AI-tool session. Check `modelstat status`.
106
+ - **`modelstat is not paired on this machine`** — run `npx modelstat@latest` first.
107
+ - **401 responses** — the bearer expired. Re-run `npx modelstat@latest`.
108
+ - **No data yet** — the agent uploads within a few seconds of your first AI-tool session. Check `npx modelstat@latest status`.
106
109
 
107
110
  ## License
108
111
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{Server as U}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as $}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as q,ListToolsRequestSchema as I}from"@modelcontextprotocol/sdk/types.js";var a=class extends Error{constructor(o,r,n){super(o);this.status=r;this.body=n;this.name="ApiError"}status;body};function k(e){if(!e.bearer)throw new Error("modelstat is not paired on this machine. Run `modelstat connect` to pair, or install the CLI first: https://modelstat.ai/install")}async function y(e,t,o,r={}){k(e);let n=new URL(o.startsWith("/")?o:`/${o}`,e.apiUrl),s=r.timeoutMs!=null?AbortSignal.timeout(r.timeoutMs):void 0,c={Authorization:`Bearer ${e.bearer}`,Accept:"application/json"};t==="POST"&&(c["content-type"]="application/json");let i=await fetch(n,{method:t,headers:c,body:r.body!==void 0?JSON.stringify(r.body):void 0,signal:s});if(!i.ok){let v=await i.text().catch(()=>"");throw new a(`${i.status} ${i.statusText} for ${n.pathname}`,i.status,v)}return await i.json()}var l={listTools(e,t={}){return y(e,"GET","/v1/mcp/tools",{timeoutMs:t.timeoutMs??1500})},callTool(e,t,o){return y(e,"POST","/v1/mcp/call",{body:{name:t,arguments:o}})}};import{execFileSync as x}from"child_process";import{mkdirSync as j,readFileSync as f,renameSync as P,unlinkSync as M,writeFileSync as C}from"fs";import{homedir as R,platform as E}from"os";import{dirname as b,join as p}from"path";function A(){let e="modelstat-agent-dev-nodejs",t=R();if(E()==="darwin")return p(t,"Library","Preferences",e,"config.json");let o=process.env.XDG_CONFIG_HOME,r=o&&o.length>0?o:p(t,".config");return p(r,e,"config.json")}function O(){try{let e=x("modelstat",["paths","--json"],{stdio:["ignore","pipe","ignore"],timeout:2e3,encoding:"utf8"}),t=JSON.parse(e);if(t.state)return{statePath:t.state,apiUrl:t.api}}catch{}return null}function u(){let e=process.env.MODELSTAT_API_URL??process.env.AGENT_API_URL,t=O(),r=process.env.MODELSTAT_STATE_FILE??t?.statePath??A(),n={};try{let c=f(r,"utf8");n=JSON.parse(c)}catch{}let s=n;return{bearer:typeof s.bearerToken=="string"?s.bearerToken:void 0,deviceId:typeof s.deviceId=="string"?s.deviceId:void 0,deviceUuid:typeof s.deviceUuid=="string"?s.deviceUuid:void 0,userEmail:typeof s.userEmail=="string"?s.userEmail:void 0,apiUrl:e??t?.apiUrl??(typeof s.apiUrl=="string"&&s.apiUrl&&s.apiUrl!=="http://localhost:3010"?s.apiUrl:"https://modelstat.ai"),statePath:r}}function S(e){return p(b(e.statePath),"mcp-tools-cache.json")}function w(e){try{let t=f(S(e),"utf8"),o=JSON.parse(t);if(Array.isArray(o.tools))return{tools:o.tools}}catch{}return null}function _(e,t){let o=S(e),r=`${o}.tmp-${process.pid}`;try{j(b(o),{recursive:!0}),C(r,JSON.stringify(t),{encoding:"utf8",mode:384}),P(r,o)}catch{try{M(r)}catch{}}}var m=["today","7d","30d","90d","mtd","ytd"],T=[{name:"get_spend_summary",description:"Return total AI token spend for this user, broken down by tool and model, for a time range.",inputSchema:{type:"object",properties:{range:{type:"string",enum:[...m],default:"7d",description:"Time range."}},required:["range"]}},{name:"get_spend_by_project",description:"Return spend grouped by auto-detected project (typically a repository).",inputSchema:{type:"object",properties:{range:{type:"string",enum:[...m],default:"7d"},project:{type:"string",description:"Optional exact project name to filter to."}},required:["range"]}},{name:"get_spend_by_tool",description:"Return spend grouped by AI tool (Claude Code, Cursor, Codex, Aider, Copilot, ChatGPT web, etc.).",inputSchema:{type:"object",properties:{range:{type:"string",enum:[...m],default:"7d"},tool:{type:"string",description:'Optional tool slug to filter to (e.g. "claude-code", "cursor").'}},required:["range"]}},{name:"list_recent_sessions",description:"Return the most recent AI sessions with their model, project, tokens, and USD cost. Useful for spotting expensive outliers.",inputSchema:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:100,default:20,description:"How many sessions to return (most recent first)."}},required:[]}},{name:"get_device_status",description:"Check whether modelstat is paired on the machine this MCP server is running on, and when the agent last reported.",inputSchema:{type:"object",properties:{},required:[]}},{name:"recommend_model",description:"Given a task description and optional budget cap, recommend the best model for THIS team based on historical session data. Returns the most-used model in the last N days that fits within the budget, plus alternatives. Useful for orchestrator agents choosing between Claude Opus / Sonnet / GPT / etc. before spawning a subagent.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Natural-language description of the task the model will do."},max_cost_usd:{type:"number",description:"Cap on the expected per-session cost (based on rolling 30d averages). Models above this are demoted to alternatives."},tool:{type:"string",description:'Optional tool filter (e.g. "claude-code", "cursor"). Omit to consider all tools.'},window_days:{type:"integer",minimum:1,maximum:365,default:30,description:"Lookback window for the usage aggregate."}},required:["task"]}},{name:"list_segments",description:"List recent segments (redacted abstracts + tags) for an org. Segments are the companion-tagged slices of a session \u2014 each carries a short pre-redacted abstract, strongly-typed tags (project / work_type / domain / etc.), tokens, and classification status. Supports filtering by project, work_type, and domain tag names.",inputSchema:{type:"object",properties:{org_id:{type:"string",description:"Org to list segments from."},project:{type:"string",description:"Filter to segments tagged with this project name."},work_type:{type:"string",description:"Filter by work_type tag (e.g. 'refactoring', 'debugging')."},domain:{type:"string",description:"Filter by domain tag."},limit:{type:"integer",minimum:1,maximum:500,default:50}},required:["org_id"]}},{name:"list_inbox",description:"Sessions awaiting org assignment \u2014 either routed to the user's inbox by their device's routing mode, or proposed into an org by a rule. Each entry includes tool, provider, git slug/branch, and message count so the user can decide where to assign it.",inputSchema:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:200,default:50}},required:[]}},{name:"list_routing_rules",description:"List the caller's multi-org routing rules in priority order. Rules route new sessions to target orgs based on tag predicates and/or semantic matching on segment abstracts. Returns each rule's target org, match mode, predicate, and enabled state.",inputSchema:{type:"object",properties:{},required:[]}},{name:"assign_session",description:"Manually reassign a session to a different org. Writes to session_routing_log with reason='manual:<user>' and cascades segments.org_id so downstream analytics pick up the new owner. Must be a member of the target org.",inputSchema:{type:"object",properties:{session_id:{type:"string"},org_id:{type:"string"}},required:["session_id","org_id"]}},{name:"get_pipeline_state",description:"Return a live snapshot of pipeline health for an org: per-task queue depths (normalize_ingest_batch, classify_segment, route_session, ...), count of segments awaiting classification, classification lag in seconds, and last-hour throughput. Useful to check whether new ingest batches are landing and being processed end-to-end.",inputSchema:{type:"object",properties:{org_id:{type:"string"}},required:["org_id"]}}],h=new U({name:"modelstat",version:"0.0.1"},{capabilities:{tools:{}}});h.setRequestHandler(I,async()=>{let e=u();try{let t=await l.listTools(e,{timeoutMs:1500});return _(e,t),g(`tools=remote count=${t.tools.length}`),{tools:t.tools}}catch(t){let o=t.message,r=w(e);return r?(g(`tools=cached count=${r.tools.length} (remote=${o})`),{tools:r.tools}):(g(`tools=static count=${T.length} (remote=${o})`),{tools:T})}});h.setRequestHandler(q,async e=>{let t=u(),o=e.params.name,r=e.params.arguments??{};try{return await l.callTool(t,o,r)}catch(n){if(n instanceof a){if(n.status===401)return d("modelstat API returned 401. Your bearer token may have expired \u2014 run `modelstat connect` to re-pair.");if(n.status===404)return d(`Tool \`${o}\` is no longer available \u2014 your MCP catalog may be out of date. Restart your MCP client to refresh.`);let s=n.body?`: ${n.body.slice(0,400)}`:"";return d(`modelstat API error (${n.status})${s}`)}return d(n.message)}});function d(e){return{isError:!0,content:[{type:"text",text:e}]}}function g(e){process.stderr.write(`modelstat-mcp: ${e}
3
- `)}async function D(){let e=new $;await h.connect(e),process.stderr.write(`modelstat-mcp: ready
4
- `)}D().catch(e=>{process.stderr.write(`modelstat-mcp: fatal: ${e.message}
2
+ import{Server as $}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as j}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as D,ListToolsRequestSchema as N}from"@modelcontextprotocol/sdk/types.js";var a=class extends Error{constructor(s,o,n){super(s);this.status=o;this.body=n;this.name="ApiError"}status;body};function x(e){if(!e.bearer)throw new Error("modelstat is not paired on this machine. Run `npx modelstat@latest` to pair, or install the CLI first: https://modelstat.ai/install")}async function y(e,t,s,o={}){x(e);let n=new URL(s.startsWith("/")?s:`/${s}`,e.apiUrl),r=o.timeoutMs!=null?AbortSignal.timeout(o.timeoutMs):void 0,c={Authorization:`Bearer ${e.bearer}`,Accept:"application/json"};t==="POST"&&(c["content-type"]="application/json");let i=await fetch(n,{method:t,headers:c,body:o.body!==void 0?JSON.stringify(o.body):void 0,signal:r});if(!i.ok){let k=await i.text().catch(()=>"");throw new a(`${i.status} ${i.statusText} for ${n.pathname}`,i.status,k)}return await i.json()}var d={listTools(e,t={}){return y(e,"GET","/v1/mcp/tools",{timeoutMs:t.timeoutMs??1500})},callTool(e,t,s){return y(e,"POST","/v1/mcp/call",{body:{name:t,arguments:s}})}};import{execFileSync as P}from"child_process";import{mkdirSync as M,readFileSync as h,renameSync as E,unlinkSync as C,writeFileSync as I}from"fs";import{homedir as R,platform as A}from"os";import{dirname as f,join as l}from"path";function U(){let e="modelstat-agent-dev-nodejs",t=R();if(A()==="darwin")return l(t,"Library","Preferences",e,"config.json");let s=process.env.XDG_CONFIG_HOME,o=s&&s.length>0?s:l(t,".config");return l(o,e,"config.json")}function O(){try{let e=P("modelstat",["paths","--json"],{stdio:["ignore","pipe","ignore"],timeout:2e3,encoding:"utf8"}),t=JSON.parse(e);if(t.state)return{statePath:t.state,apiUrl:t.api}}catch{}return null}function u(){let e=process.env.MODELSTAT_API_URL??process.env.AGENT_API_URL,t=O(),o=process.env.MODELSTAT_STATE_FILE??t?.statePath??U(),n={};try{let c=h(o,"utf8");n=JSON.parse(c)}catch{}let r=n;return{bearer:typeof r.bearerToken=="string"?r.bearerToken:void 0,deviceId:typeof r.deviceId=="string"?r.deviceId:void 0,deviceUuid:typeof r.deviceUuid=="string"?r.deviceUuid:void 0,userEmail:typeof r.userEmail=="string"?r.userEmail:void 0,apiUrl:e??t?.apiUrl??(typeof r.apiUrl=="string"&&r.apiUrl&&r.apiUrl!=="http://localhost:3010"?r.apiUrl:"https://modelstat.ai"),statePath:o}}function v(e){return l(f(e.statePath),"mcp-tools-cache.json")}function S(e){try{let t=h(v(e),"utf8"),s=JSON.parse(t);if(Array.isArray(s.tools))return{tools:s.tools}}catch{}return null}function b(e,t){let s=v(e),o=`${s}.tmp-${process.pid}`;try{M(f(s),{recursive:!0}),I(o,JSON.stringify(t),{encoding:"utf8",mode:384}),E(o,s)}catch{try{C(o)}catch{}}}var L=["today","7d","30d","90d","mtd","ytd"],T=["provider","model","tool","day","hour","device","identity","session"],F=["cost","list","tokens","events","sessions","tokens_input","tokens_output","tokens_cache_read","tokens_cache_creation","tokens_reasoning"],_={range:{type:"string",enum:[...L],description:"Named time window (ignored when from/to given). Omit range AND from/to for all-time."},from:{type:"string",description:"RFC3339 inclusive lower bound (overrides `range`)"},to:{type:"string",description:"RFC3339 exclusive upper bound (overrides `range`)"}},w=[{name:"usage_overview",description:"Spend/usage headline for the account: effective cost, list-price cost, savings, total tokens, event count, distinct sessions. Start here for 'how much did I spend?'. Costs are exact decimal strings in USD.",inputSchema:{type:"object",properties:{..._}}},{name:"usage_explore",description:"The charting workhorse: group-by (and optionally stack-by) any dimension, pick a metric, filter, and get back cells + whole-set totals. Time series: group_by=day or hour (cells come back chronologically \u2014 ideal for line/bar charts); stacked series: add stack_by=model|tool|provider. Leaderboards: group_by=model|tool|session etc. (sorted by value). Token-class metrics (tokens_input, tokens_output, tokens_cache_read, tokens_cache_creation, tokens_reasoning) split the raw token volume \u2014 e.g. cache-hit-rate = tokens_cache_read vs tokens. Filters (providers/models/tools/session_ids) are exact-match lists; session_ids scopes everything to those sessions (pass a whole compaction chain for one logical conversation). Cost values are exact decimal USD strings.",inputSchema:{type:"object",properties:{group_by:{type:"string",enum:[...T],default:"day"},stack_by:{type:"string",enum:[...T],description:"Optional second dimension; each cell carries `stack`."},metric:{type:"string",enum:[...F],default:"cost"},providers:{type:"array",items:{type:"string"},description:'e.g. ["anthropic"]'},models:{type:"array",items:{type:"string"}},tools:{type:"array",items:{type:"string"},description:'e.g. ["claude_code", "cursor"]'},session_ids:{type:"array",items:{type:"string"}},limit:{type:"integer",minimum:1,maximum:500,default:50,description:"Top-N cap on returned groups."},..._}}},{name:"list_sessions",description:"The account's sessions, most recent activity first (cursor-paginated). Each row: session_id, tool, total tokens, effective cost. Use session_detail or sessions_usage to drill in.",inputSchema:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:500},cursor:{type:"string"}}}},{name:"session_detail",description:"One session with its full token breakdown and its segments (time-bounded slices with redacted abstracts + tags). The segment abstracts tell you WHAT the session was about.",inputSchema:{type:"object",required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"sessions_usage",description:"Aggregate usage over an EXPLICIT set of session ids: per-session rows (tool, time bounds, per-class tokens, cost) plus a combined roll-up. Built for 'current session' analysis in Claude Code: one logical conversation spans several session ids across compactions/resumes \u2014 pass every sessionId found in the transcript chain and read the combined block. Ids not (yet) ingested are listed in missing_session_ids.",inputSchema:{type:"object",required:["session_ids"],properties:{session_ids:{type:"array",items:{type:"string"},minItems:1,maxItems:200}}}},{name:"assign_session",description:"MUTATING: reassign a session's owner/identity.",inputSchema:{type:"object",required:["session_id","target"],properties:{session_id:{type:"string"},target:{type:"string",description:"identity/owner to assign"}}}}],g=new $({name:"modelstat",version:"0.0.2"},{capabilities:{tools:{}}});g.setRequestHandler(N,async()=>{let e=u();try{let t=await d.listTools(e,{timeoutMs:1500});return b(e,t),m(`tools=remote count=${t.tools.length}`),{tools:t.tools}}catch(t){let s=t.message,o=S(e);return o?(m(`tools=cached count=${o.tools.length} (remote=${s})`),{tools:o.tools}):(m(`tools=static count=${w.length} (remote=${s})`),{tools:w})}});g.setRequestHandler(D,async e=>{let t=u(),s=e.params.name,o=e.params.arguments??{};try{return await d.callTool(t,s,o)}catch(n){if(n instanceof a){if(n.status===401)return p("modelstat API returned 401. Your bearer token may have expired \u2014 run `npx modelstat@latest` to re-pair.");if(n.status===404)return p(`Tool \`${s}\` is no longer available \u2014 your MCP catalog may be out of date. Restart your MCP client to refresh.`);let r=n.body?`: ${n.body.slice(0,400)}`:"";return p(`modelstat API error (${n.status})${r}`)}return p(n.message)}});function p(e){return{isError:!0,content:[{type:"text",text:e}]}}function m(e){process.stderr.write(`modelstat-mcp: ${e}
3
+ `)}async function G(){let e=new j;await g.connect(e),process.stderr.write(`modelstat-mcp: ready
4
+ `)}G().catch(e=>{process.stderr.write(`modelstat-mcp: fatal: ${e.message}
5
5
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@modelstat/mcp",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "MCP server for modelstat — ask any MCP-compatible AI tool about your token spend.",
5
- "keywords": ["mcp", "modelcontextprotocol", "modelstat", "ai", "observability", "token-tracking"],
5
+ "keywords": [
6
+ "mcp",
7
+ "modelcontextprotocol",
8
+ "modelstat",
9
+ "ai",
10
+ "observability",
11
+ "token-tracking"
12
+ ],
6
13
  "homepage": "https://modelstat.ai/mcp",
7
14
  "repository": {
8
15
  "type": "git",
@@ -15,15 +22,18 @@
15
22
  "bin": {
16
23
  "modelstat-mcp": "./dist/index.js"
17
24
  },
18
- "files": ["dist/**/*", "README.md", "LICENSE"],
19
- "scripts": {
20
- "build": "tsup src/index.ts --format esm --out-dir dist --clean --no-splitting --minify",
21
- "dev": "tsx src/index.ts",
22
- "typecheck": "tsc --noEmit",
23
- "prepack": "pnpm build"
25
+ "files": [
26
+ "dist/**/*",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
24
32
  },
25
- "engines": { "node": ">=20" },
26
- "os": ["darwin", "linux"],
33
+ "os": [
34
+ "darwin",
35
+ "linux"
36
+ ],
27
37
  "dependencies": {
28
38
  "@modelcontextprotocol/sdk": "^1.0.0"
29
39
  },
@@ -31,5 +41,10 @@
31
41
  "tsup": "^8.3.5",
32
42
  "tsx": "^4.19.2",
33
43
  "typescript": "^5.7.3"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup src/index.ts --format esm --out-dir dist --clean --no-splitting --minify",
47
+ "dev": "tsx src/index.ts",
48
+ "typecheck": "tsc --noEmit"
34
49
  }
35
- }
50
+ }