@rotifer/mcp-server 0.2.0 → 0.3.0

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/dist/auth.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ export type AuthProvider = "github" | "gitlab";
2
+ export interface CloudUser {
3
+ id: string;
4
+ username: string;
5
+ avatar_url: string | null;
6
+ provider_id: string;
7
+ }
8
+ export interface CloudCredentials {
9
+ access_token: string;
10
+ refresh_token: string;
11
+ expires_at: number;
12
+ provider: AuthProvider;
13
+ user: CloudUser;
14
+ }
15
+ export declare function loadCredentials(): CloudCredentials | null;
16
+ export declare function saveCredentials(creds: CloudCredentials): void;
17
+ export declare function clearCredentials(): void;
18
+ export declare function generateCodeVerifier(): string;
19
+ export declare function generateCodeChallenge(verifier: string): string;
20
+ /**
21
+ * Start a local HTTP server to receive the OAuth callback.
22
+ * Handles both PKCE flow (?code=...) and implicit flow (#access_token=...).
23
+ */
24
+ export declare function waitForOAuthCallback(port?: number): Promise<string>;
package/dist/auth.js ADDED
@@ -0,0 +1,107 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createServer } from "node:http";
4
+ import { randomBytes, createHash } from "node:crypto";
5
+ const ROTIFER_HOME = join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".rotifer");
6
+ function ensureRotiferHome() {
7
+ if (!existsSync(ROTIFER_HOME)) {
8
+ mkdirSync(ROTIFER_HOME, { recursive: true });
9
+ }
10
+ }
11
+ function credentialsPath() {
12
+ return join(ROTIFER_HOME, "credentials.json");
13
+ }
14
+ export function loadCredentials() {
15
+ const path = credentialsPath();
16
+ if (!existsSync(path))
17
+ return null;
18
+ try {
19
+ const data = JSON.parse(readFileSync(path, "utf-8"));
20
+ if (data.expires_at && Date.now() > data.expires_at)
21
+ return null;
22
+ return data;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export function saveCredentials(creds) {
29
+ ensureRotiferHome();
30
+ writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2) + "\n", {
31
+ mode: 0o600,
32
+ });
33
+ }
34
+ export function clearCredentials() {
35
+ const path = credentialsPath();
36
+ if (existsSync(path)) {
37
+ const { unlinkSync } = require("node:fs");
38
+ unlinkSync(path);
39
+ }
40
+ }
41
+ export function generateCodeVerifier() {
42
+ return randomBytes(32).toString("base64url");
43
+ }
44
+ export function generateCodeChallenge(verifier) {
45
+ return createHash("sha256").update(verifier).digest("base64url");
46
+ }
47
+ /**
48
+ * Start a local HTTP server to receive the OAuth callback.
49
+ * Handles both PKCE flow (?code=...) and implicit flow (#access_token=...).
50
+ */
51
+ export function waitForOAuthCallback(port = 9876) {
52
+ return new Promise((resolve, reject) => {
53
+ const server = createServer((req, res) => {
54
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
55
+ if (url.pathname === "/callback/token") {
56
+ const token = url.searchParams.get("access_token");
57
+ const refresh = url.searchParams.get("refresh_token");
58
+ if (token) {
59
+ res.writeHead(200, { "Content-Type": "text/html" });
60
+ res.end("<html><body><h2>Login successful!</h2>" +
61
+ "<p>You can close this window and return to the terminal.</p>" +
62
+ "</body></html>");
63
+ server.close();
64
+ resolve(`implicit:${token}:${refresh || ""}`);
65
+ return;
66
+ }
67
+ }
68
+ const code = url.searchParams.get("code");
69
+ if (code) {
70
+ res.writeHead(200, { "Content-Type": "text/html" });
71
+ res.end("<html><body><h2>Login successful!</h2>" +
72
+ "<p>You can close this window and return to the terminal.</p>" +
73
+ "</body></html>");
74
+ server.close();
75
+ resolve(code);
76
+ }
77
+ else {
78
+ res.writeHead(200, { "Content-Type": "text/html" });
79
+ res.end(`<html><body><script>
80
+ if (window.location.hash) {
81
+ var params = new URLSearchParams(window.location.hash.substring(1));
82
+ var token = params.get('access_token');
83
+ var refresh = params.get('refresh_token');
84
+ if (token) {
85
+ window.location.href = '/callback/token?access_token=' + encodeURIComponent(token) + '&refresh_token=' + encodeURIComponent(refresh || '');
86
+ } else {
87
+ document.body.innerHTML = '<h2>Login failed</h2><p>No token received.</p>';
88
+ }
89
+ } else {
90
+ document.body.innerHTML = '<h2>Login failed</h2><p>Missing authorization data.</p>';
91
+ }
92
+ </script><noscript>Enable JavaScript to complete login.</noscript></body></html>`);
93
+ }
94
+ });
95
+ server.listen(port, () => {
96
+ // Server ready
97
+ });
98
+ server.on("error", (err) => {
99
+ reject(new Error(`Failed to start callback server: ${err.message}`));
100
+ });
101
+ setTimeout(() => {
102
+ server.close();
103
+ reject(new Error("Login timed out after 120 seconds"));
104
+ }, 120_000);
105
+ });
106
+ }
107
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,22 @@
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createServer } from "./server.js";
4
4
  async function main() {
5
+ const subcommand = process.argv[2];
6
+ if (subcommand === "login") {
7
+ const { runLogin } = await import("./login.js");
8
+ const providerIdx = process.argv.indexOf("--provider");
9
+ const endpointIdx = process.argv.indexOf("--endpoint");
10
+ await runLogin({
11
+ provider: providerIdx !== -1 ? process.argv[providerIdx + 1] : undefined,
12
+ endpoint: endpointIdx !== -1 ? process.argv[endpointIdx + 1] : undefined,
13
+ });
14
+ return;
15
+ }
16
+ if (subcommand === "logout") {
17
+ const { runLogout } = await import("./login.js");
18
+ runLogout();
19
+ return;
20
+ }
5
21
  const server = createServer();
6
22
  const transport = new StdioServerTransport();
7
23
  await server.connect(transport);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,QAAQ,CAAC;YACb,QAAQ,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACxE,QAAQ,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SACzE,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACjD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function runLogin(options: {
2
+ provider?: string;
3
+ endpoint?: string;
4
+ }): Promise<void>;
5
+ export declare function runLogout(): void;
package/dist/login.js ADDED
@@ -0,0 +1,106 @@
1
+ import { loadCloudConfig } from "./cloud.js";
2
+ import { openBrowser } from "./open-browser.js";
3
+ import { loadCredentials, saveCredentials, clearCredentials, generateCodeVerifier, generateCodeChallenge, waitForOAuthCallback, } from "./auth.js";
4
+ const SUPPORTED_PROVIDERS = ["github", "gitlab"];
5
+ export async function runLogin(options) {
6
+ console.log("Rotifer Cloud Login\n");
7
+ const existing = loadCredentials();
8
+ if (existing) {
9
+ console.log(`Already logged in as ${existing.user.username} (via ${existing.provider})`);
10
+ console.log("Run 'rotifer-mcp-server logout' to switch accounts.");
11
+ return;
12
+ }
13
+ const provider = (options.provider || "gitlab");
14
+ if (!SUPPORTED_PROVIDERS.includes(provider)) {
15
+ console.error(`Unsupported provider: '${provider}'. Supported: ${SUPPORTED_PROVIDERS.join(", ")}`);
16
+ process.exit(1);
17
+ }
18
+ const config = loadCloudConfig();
19
+ const endpoint = options.endpoint || config.endpoint;
20
+ console.log(`Opening browser for ${provider} authorization...`);
21
+ const callbackPort = 9876;
22
+ const codeVerifier = generateCodeVerifier();
23
+ const codeChallenge = generateCodeChallenge(codeVerifier);
24
+ const authUrl = `${endpoint}/auth/v1/authorize?provider=${provider}` +
25
+ `&redirect_to=http://localhost:${callbackPort}/callback` +
26
+ `&code_challenge=${codeChallenge}` +
27
+ `&code_challenge_method=S256`;
28
+ openBrowser(authUrl);
29
+ console.log("Waiting for authorization (timeout: 120s)...");
30
+ try {
31
+ const callbackResult = await waitForOAuthCallback(callbackPort);
32
+ let accessToken;
33
+ let refreshToken;
34
+ if (callbackResult.startsWith("implicit:")) {
35
+ const parts = callbackResult.split(":");
36
+ accessToken = parts.slice(1, -1).join(":");
37
+ refreshToken = parts[parts.length - 1];
38
+ }
39
+ else {
40
+ console.log("Exchanging authorization code for token...");
41
+ const tokenRes = await fetch(`${endpoint}/auth/v1/token?grant_type=pkce`, {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ apikey: config.anonKey,
46
+ },
47
+ body: JSON.stringify({
48
+ auth_code: callbackResult,
49
+ code_verifier: codeVerifier,
50
+ }),
51
+ });
52
+ if (!tokenRes.ok) {
53
+ const err = await tokenRes.text();
54
+ console.error(`Authentication failed: ${err}`);
55
+ process.exit(1);
56
+ }
57
+ const tokenData = (await tokenRes.json());
58
+ accessToken = tokenData.access_token;
59
+ refreshToken = tokenData.refresh_token;
60
+ }
61
+ const userRes = await fetch(`${endpoint}/auth/v1/user`, {
62
+ headers: {
63
+ Authorization: `Bearer ${accessToken}`,
64
+ apikey: config.anonKey,
65
+ },
66
+ });
67
+ const userData = (await userRes.json());
68
+ const meta = userData.user_metadata || {};
69
+ const username = meta.user_name ||
70
+ meta.preferred_username ||
71
+ meta.name ||
72
+ meta.nickname ||
73
+ meta.email?.split("@")[0] ||
74
+ "unknown";
75
+ saveCredentials({
76
+ access_token: accessToken,
77
+ refresh_token: refreshToken,
78
+ expires_at: Date.now() + 3600 * 1000,
79
+ provider,
80
+ user: {
81
+ id: userData.id,
82
+ username,
83
+ avatar_url: meta.avatar_url || null,
84
+ provider_id: meta.provider_id || meta.sub || "",
85
+ },
86
+ });
87
+ console.log();
88
+ console.log(`Logged in as ${username} (via ${provider})`);
89
+ console.log(`Endpoint: ${endpoint}`);
90
+ console.log("You can now use Rotifer Cloud features via MCP.");
91
+ }
92
+ catch (err) {
93
+ console.error(err.message || "Login failed");
94
+ process.exit(1);
95
+ }
96
+ }
97
+ export function runLogout() {
98
+ const existing = loadCredentials();
99
+ if (!existing) {
100
+ console.log("Not currently logged in.");
101
+ return;
102
+ }
103
+ clearCredentials();
104
+ console.log(`Logged out (was: ${existing.user.username} via ${existing.provider})`);
105
+ }
106
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Build the shell command to open a URL in the default browser.
3
+ * Exported separately for testability.
4
+ */
5
+ export declare function buildOpenCommand(url: string, platform: string): string;
6
+ /**
7
+ * Open a URL in the user's default browser, cross-platform.
8
+ */
9
+ export declare function openBrowser(url: string): void;
@@ -0,0 +1,19 @@
1
+ import { exec } from "node:child_process";
2
+ /**
3
+ * Build the shell command to open a URL in the default browser.
4
+ * Exported separately for testability.
5
+ */
6
+ export function buildOpenCommand(url, platform) {
7
+ if (platform === "win32") {
8
+ return `start "" "${url}"`;
9
+ }
10
+ const bin = platform === "darwin" ? "open" : "xdg-open";
11
+ return `${bin} "${url}"`;
12
+ }
13
+ /**
14
+ * Open a URL in the user's default browser, cross-platform.
15
+ */
16
+ export function openBrowser(url) {
17
+ exec(buildOpenCommand(url, process.platform));
18
+ }
19
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,QAAgB;IAC5D,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,aAAa,GAAG,GAAG,CAAC;IAC7B,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IACxD,OAAO,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChD,CAAC"}
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourceTemplatesReq
3
3
  import { searchGenes, getGeneDetail, arenaRankings, compareGenes, geneStats, leaderboard, developerProfile, listLocalGenes } from "./tools.js";
4
4
  import { getGeneStatsRpc, getReputationLeaderboard, getDeveloperProfile, getGene } from "./cloud.js";
5
5
  export function createServer() {
6
- const server = new Server({ name: "rotifer", version: "0.2.0" }, { capabilities: { tools: {}, resources: {} } });
6
+ const server = new Server({ name: "rotifer", version: "0.3.0" }, { capabilities: { tools: {}, resources: {} } });
7
7
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
8
8
  tools: [
9
9
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rotifer/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Rotifer Protocol MCP Server — search, compare, and explore Genes via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {