@modelstat/mcp 0.0.1 → 0.0.3
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/LICENSE +87 -0
- package/README.md +15 -12
- package/dist/index.js +3 -3
- 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
|
|
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
|
|
81
|
+
All tools are **read-only** except `assign_session`.
|
|
82
82
|
|
|
83
83
|
| Tool | Purpose |
|
|
84
84
|
|---|---|
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
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
|
|
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 `
|
|
104
|
-
- **401 responses** — the bearer expired. Re-run `modelstat
|
|
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
|
|
3
|
-
`)}async function
|
|
4
|
-
`)}
|
|
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 O(){let e="modelstat-daemon-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 U(){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.DAEMON_API_URL,t=U(),o=process.env.MODELSTAT_STATE_FILE??t?.statePath??O(),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 q(){let e=new j;await g.connect(e),process.stderr.write(`modelstat-mcp: ready
|
|
4
|
+
`)}q().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.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "MCP server for modelstat — ask any MCP-compatible AI tool about your token spend.",
|
|
5
|
-
"keywords": [
|
|
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": [
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/**/*",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
24
32
|
},
|
|
25
|
-
"
|
|
26
|
-
|
|
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
|
+
}
|