@ronana/mcp 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.
Files changed (2) hide show
  1. package/dist/oauth.js +29 -11
  2. package/package.json +1 -1
package/dist/oauth.js CHANGED
@@ -26,15 +26,19 @@ export function readCredentials() {
26
26
  function writeCredentials(value) {
27
27
  entry().setPassword(JSON.stringify(value));
28
28
  }
29
+ /** Ends a stored sign-in on the server, so it leaves the account's connections.
30
+ * Best effort: an unreachable server still lets the local step go ahead. */
31
+ async function revoke(credentials) {
32
+ await fetch(`${credentials.baseUrl}/api/auth/oauth2/revoke`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
35
+ body: new URLSearchParams({ token: credentials.refreshToken, client_id: credentials.clientId }),
36
+ }).catch(() => null);
37
+ }
29
38
  export async function logout() {
30
39
  const current = readCredentials();
31
- if (current) {
32
- await fetch(`${current.baseUrl}/api/auth/oauth2/revoke`, {
33
- method: "POST",
34
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
35
- body: new URLSearchParams({ token: current.refreshToken, client_id: current.clientId }),
36
- }).catch(() => null);
37
- }
40
+ if (current)
41
+ await revoke(current);
38
42
  try {
39
43
  entry().deletePassword();
40
44
  }
@@ -83,11 +87,13 @@ export async function login(baseUrl = process.env.RONANA_BASE_URL ?? "https://ro
83
87
  const close = () => { clearTimeout(timer); server.close(); };
84
88
  const server = createServer((req, res) => {
85
89
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
86
- if (url.searchParams.get("state") !== state) {
87
- res.writeHead(400).end("Invalid OAuth state.");
88
- fail(new Error("OAuth state did not match."));
90
+ // Anything but our own callback (a favicon, a prefetch, a stale tab from an
91
+ // earlier sign-in) is ignored, so it cannot end the sign-in it isn't part of.
92
+ if (url.pathname !== "/callback" || url.searchParams.get("state") !== state) {
93
+ res.writeHead(404).end();
94
+ return;
89
95
  }
90
- else if (url.searchParams.get("error")) {
96
+ if (url.searchParams.get("error")) {
91
97
  res.writeHead(400).end("Authorization was not completed.");
92
98
  fail(new Error(url.searchParams.get("error_description") ?? url.searchParams.get("error")));
93
99
  }
@@ -154,6 +160,9 @@ ${authorize}
154
160
  });
155
161
  if (typeof tokens.access_token !== "string" || typeof tokens.refresh_token !== "string")
156
162
  throw new Error("OAuth response did not contain tokens.");
163
+ // Signing in again replaces the stored sign-in; end the old one too, or
164
+ // every login leaves another connection behind on the account.
165
+ const previous = readCredentials();
157
166
  writeCredentials({
158
167
  baseUrl,
159
168
  clientId: registered.client_id,
@@ -161,6 +170,8 @@ ${authorize}
161
170
  refreshToken: tokens.refresh_token,
162
171
  expiresAt: Date.now() + Number(tokens.expires_in ?? 900) * 1000,
163
172
  });
173
+ if (previous && previous.clientId !== registered.client_id)
174
+ await revoke(previous);
164
175
  }
165
176
  finally {
166
177
  callback.close();
@@ -220,6 +231,13 @@ export async function accessToken() {
220
231
  client_id: current.clientId,
221
232
  refresh_token: current.refreshToken,
222
233
  resource: `${current.baseUrl}/mcp`,
234
+ }).catch((error) => {
235
+ // A network failure (fetch's TypeError) may pass; a refusal means this
236
+ // sign-in is over (disconnected on the website, or expired), and the
237
+ // server's own words ("session not found") would read as something else.
238
+ if (error instanceof TypeError)
239
+ throw error;
240
+ throw new Error("Your Ronana sign-in has ended (disconnected or expired). Run `ronana auth login` again.");
223
241
  });
224
242
  if (typeof tokens.access_token !== "string")
225
243
  throw new Error("OAuth refresh failed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronana/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Ronana command line and local MCP bridge.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ronana.ai",