@modelstat/mcp 0.0.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +3 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelstat/mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "MCP server for modelstat — ask any MCP-compatible AI tool about your token spend.",
5
5
  "keywords": [
6
6
  "mcp",