mcpcost 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -11,7 +11,7 @@ Every MCP server you configure hands your agent its tool definitions at the star
11
11
  ```
12
12
  $ npx mcpcost --config .mcp.json --tools 3
13
13
 
14
- mcpcost 0.3.0 · tool definitions your agent loads before the first prompt
14
+ mcpcost 0.3.1 · tool definitions your agent loads before the first prompt
15
15
  .mcp.json
16
16
 
17
17
  server source result tools tokens of 200k
@@ -1 +1,3 @@
1
+ /** The default callback port. Fixed, because the registered client's redirect URI carries it; a random port would need a new registration every time. */
2
+ export declare const DEFAULT_CALLBACK_PORT = 41379;
1
3
  export declare function authCommand(argv: string[]): Promise<number>;
package/dist/auth-cli.js CHANGED
@@ -20,8 +20,9 @@ Usage
20
20
  Options
21
21
  --credentials <file> the credentials file (default ~/.mcpcost/credentials.json, or $MCPCOST_CREDENTIALS)
22
22
  --scope <scope> scope to ask for in the OAuth flow
23
+ --port <n> local port for the callback (default 41379; the registered client is bound to it)
23
24
  --no-open print the authorization URL, do not open the browser
24
- --timeout <ms> how long to wait for the consent (default 300000)
25
+ --timeout <ms> how long to wait for the consent (default 600000)
25
26
 
26
27
  After the consent the server is measured once with the new credentials, so you see that it works.
27
28
  The daily board uses the same file through its MCPCOST credentials table; see the README.`;
@@ -36,8 +37,10 @@ function openBrowser(url) {
36
37
  // The URL is printed too.
37
38
  }
38
39
  }
40
+ /** The default callback port. Fixed, because the registered client's redirect URI carries it; a random port would need a new registration every time. */
41
+ export const DEFAULT_CALLBACK_PORT = 41379;
39
42
  /** Waits for one GET /callback?code=…&state=… on a local port. */
40
- function callbackServer(expectedState, timeoutMs) {
43
+ function callbackServer(expectedState, timeoutMs, port) {
41
44
  return new Promise((resolveServer, rejectServer) => {
42
45
  let resolveCode = () => undefined;
43
46
  let rejectCode = () => undefined;
@@ -70,10 +73,12 @@ function callbackServer(expectedState, timeoutMs) {
70
73
  });
71
74
  const timer = setTimeout(() => rejectCode(new Error(`no callback within ${Math.round(timeoutMs / 1000)} s`)), timeoutMs);
72
75
  timer.unref();
73
- server.on('error', rejectServer);
74
- server.listen(0, '127.0.0.1', () => {
75
- const port = server.address().port;
76
- resolveServer({ redirectUrl: `http://127.0.0.1:${port}/callback`, code, close: () => { clearTimeout(timer); server.close(); } });
76
+ server.on('error', (err) => {
77
+ rejectServer(err.code === 'EADDRINUSE' ? new Error(`port ${port} is in use; pass --port <n> (the registered client is bound to the port)`) : err);
78
+ });
79
+ server.listen(port, '127.0.0.1', () => {
80
+ const bound = server.address().port;
81
+ resolveServer({ redirectUrl: `http://127.0.0.1:${bound}/callback`, code, close: () => { clearTimeout(timer); server.close(); } });
77
82
  });
78
83
  });
79
84
  }
@@ -86,8 +91,9 @@ export async function authCommand(argv) {
86
91
  list: { type: 'boolean', default: false },
87
92
  remove: { type: 'boolean', default: false },
88
93
  scope: { type: 'string' },
94
+ port: { type: 'string' },
89
95
  'no-open': { type: 'boolean', default: false },
90
- timeout: { type: 'string', default: '300000' },
96
+ timeout: { type: 'string', default: '600000' },
91
97
  help: { type: 'boolean', default: false }
92
98
  },
93
99
  allowPositionals: true
@@ -145,8 +151,8 @@ export async function authCommand(argv) {
145
151
  return verify(store, key);
146
152
  }
147
153
  // The OAuth flow: discovery, registration, the consent in the browser, the token.
148
- const timeoutMs = Number(values.timeout) || 300_000;
149
- const cb = await callbackServer(() => provider.lastState, timeoutMs);
154
+ const timeoutMs = Number(values.timeout) || 600_000;
155
+ const cb = await callbackServer(() => provider.lastState, timeoutMs, Number(values.port) || DEFAULT_CALLBACK_PORT);
150
156
  const provider = createProvider(store, key, {
151
157
  redirectUrl: cb.redirectUrl,
152
158
  scope: values.scope,
@@ -154,7 +160,7 @@ export async function authCommand(argv) {
154
160
  console.log(`\nOpen this URL and allow the access:\n ${url}\n`);
155
161
  if (!values['no-open'])
156
162
  openBrowser(url.toString());
157
- console.log('Waiting for the consent…');
163
+ console.log(`Waiting for the consent, up to ${Math.round(timeoutMs / 60_000)} minutes…`);
158
164
  }
159
165
  });
160
166
  try {
package/dist/core.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
2
  import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
3
3
  import type { ProbeResult, ProbeStatus, RemoteTransport } from './types.js';
4
- export declare const VERSION = "0.3.0";
5
- export declare const USER_AGENT = "mcpcost/0.3.0 (mcpcost; +https://github.com/ondraulehla/mcpcost)";
4
+ export declare const VERSION = "0.3.1";
5
+ export declare const USER_AGENT = "mcpcost/0.3.1 (mcpcost; +https://github.com/ondraulehla/mcpcost)";
6
6
  export interface RawTool {
7
7
  name: string;
8
8
  description?: string;
package/dist/core.js CHANGED
@@ -6,7 +6,7 @@
6
6
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
7
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
8
8
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
9
- export const VERSION = '0.3.0';
9
+ export const VERSION = '0.3.1';
10
10
  export const USER_AGENT = `mcpcost/${VERSION} (mcpcost; +https://github.com/ondraulehla/mcpcost)`;
11
11
  export function emptyResult(url, transport) {
12
12
  return { url, transport, probedAt: new Date().toISOString(), status: 'error', latencyMs: {} };
@@ -69,6 +69,14 @@ export function createProvider(store, url, options = {}) {
69
69
  oauth.scope = options.scope;
70
70
  const redirectUrl = options.redirectUrl ?? 'http://127.0.0.1/callback';
71
71
  let changed = false;
72
+ // A client registered for another redirect URI (an earlier port) cannot be used; register again.
73
+ if (options.onRedirect && oauth.clientInformation && !oauth.tokens) {
74
+ const uris = oauth.clientInformation.redirect_uris;
75
+ if (uris && !uris.includes(redirectUrl)) {
76
+ delete oauth.clientInformation;
77
+ changed = true;
78
+ }
79
+ }
72
80
  let verifier;
73
81
  let state;
74
82
  const touched = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpcost",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "What do your MCP servers cost in context tokens? Measures every server in your config, exact counts for Claude, a budget for CI.",
5
5
  "keywords": [
6
6
  "mcp",