@goke/mcp 0.0.4

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.
@@ -0,0 +1,77 @@
1
+ import type { OAuthTokens, OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ /**
3
+ * Persisted OAuth state for file-based storage
4
+ */
5
+ export interface McpOAuthState {
6
+ tokens?: OAuthTokens;
7
+ clientInformation?: OAuthClientInformation;
8
+ codeVerifier?: string;
9
+ serverUrl?: string;
10
+ }
11
+ /**
12
+ * OAuth configuration for addMcpCommands
13
+ */
14
+ export interface McpOAuthConfig {
15
+ /** Client name shown during OAuth consent screen */
16
+ clientName: string;
17
+ /**
18
+ * Load persisted OAuth state from storage (e.g., config file)
19
+ */
20
+ load: () => McpOAuthState | undefined;
21
+ /**
22
+ * Save OAuth state to storage. Called after successful auth or token refresh.
23
+ * Pass undefined to clear the state (logout).
24
+ */
25
+ save: (state: McpOAuthState | undefined) => void;
26
+ /**
27
+ * Called with the authorization URL. Default behavior opens the browser.
28
+ * Override to customize (e.g., just print the URL).
29
+ */
30
+ onAuthUrl?: (url: string) => void;
31
+ /**
32
+ * Called on successful authentication
33
+ */
34
+ onAuthSuccess?: () => void;
35
+ /**
36
+ * Called on authentication error
37
+ */
38
+ onAuthError?: (error: string) => void;
39
+ }
40
+ /**
41
+ * Result of startOAuthFlow
42
+ */
43
+ export interface OAuthFlowResult {
44
+ success: boolean;
45
+ state?: McpOAuthState;
46
+ error?: string;
47
+ }
48
+ /**
49
+ * Options for starting OAuth flow
50
+ */
51
+ export interface StartOAuthFlowOptions {
52
+ serverUrl: string;
53
+ clientName: string;
54
+ /** Existing OAuth state (for re-auth scenarios) */
55
+ existingState?: McpOAuthState;
56
+ /** Called with auth URL, default opens browser */
57
+ onAuthUrl?: (url: string) => void;
58
+ /** Timeout in ms waiting for callback, default 5 minutes */
59
+ timeout?: number;
60
+ }
61
+ /**
62
+ * Options for the local callback server
63
+ */
64
+ export interface CallbackServerOptions {
65
+ /** Called when server starts with the redirect URI */
66
+ onReady?: (redirectUri: string) => void;
67
+ /** Timeout in ms, default 5 minutes */
68
+ timeout?: number;
69
+ }
70
+ /**
71
+ * Result from callback server
72
+ */
73
+ export interface CallbackResult {
74
+ code: string;
75
+ state: string;
76
+ }
77
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEpG;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,IAAI,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IAEtC;;;OAGG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,CAAC;IAEjD;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAE3B;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kDAAkD;IAClD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@goke/mcp",
3
+ "version": "0.0.4",
4
+ "description": "Dynamically generate CLI commands from MCP server tools",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./src/index.js": {
14
+ "types": "./src/index.ts",
15
+ "import": "./src/index.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "keywords": [
23
+ "mcp",
24
+ "cli",
25
+ "goke"
26
+ ],
27
+ "author": "Tommaso De Rossi, morse <beats.by.morse@gmail.com>",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.0",
31
+ "js-yaml": "^4.1.1"
32
+ },
33
+ "peerDependencies": {
34
+ "goke": "*"
35
+ },
36
+ "devDependencies": {
37
+ "@types/js-yaml": "^4.0.9",
38
+ "@types/node": "^22.19.7",
39
+ "goke": "6.1.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "watch": "tsc -w"
44
+ }
45
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
2
+ import { FileOAuthProvider } from "./oauth-provider.js";
3
+ import { startCallbackServer } from "./local-callback-server.js";
4
+ import type { McpOAuthState, OAuthFlowResult, StartOAuthFlowOptions } from "./types.js";
5
+
6
+ /**
7
+ * Open a URL in the default browser.
8
+ * Uses platform-specific commands.
9
+ */
10
+ async function openBrowser(url: string): Promise<void> {
11
+ const { exec } = await import("node:child_process");
12
+ const { promisify } = await import("node:util");
13
+ const execAsync = promisify(exec);
14
+
15
+ const platform = process.platform;
16
+ const command = (() => {
17
+ if (platform === "darwin") {
18
+ return `open "${url}"`;
19
+ }
20
+ if (platform === "win32") {
21
+ return `start "" "${url}"`;
22
+ }
23
+ // Linux and others
24
+ return `xdg-open "${url}"`;
25
+ })();
26
+
27
+ await execAsync(command);
28
+ }
29
+
30
+ /**
31
+ * Start the OAuth flow for an MCP server.
32
+ * This is an internal function - consumers should not call this directly.
33
+ * It is automatically triggered by addMcpCommands when a 401 error occurs.
34
+ *
35
+ * This function:
36
+ * 1. Starts a local callback server on a random port
37
+ * 2. Initiates OAuth with the MCP server
38
+ * 3. Opens the browser for user authorization
39
+ * 4. Waits for the callback with the authorization code
40
+ * 5. Exchanges the code for tokens
41
+ * 6. Returns the OAuth state for persistence
42
+ */
43
+ export async function startOAuthFlow(options: StartOAuthFlowOptions): Promise<OAuthFlowResult> {
44
+ const { serverUrl, clientName, existingState, onAuthUrl, timeout } = options;
45
+
46
+ // Start local callback server on random port
47
+ const callbackServer = await startCallbackServer({ timeout });
48
+ const { redirectUri, waitForCallback, close } = callbackServer;
49
+
50
+ try {
51
+ // Create OAuth provider with the dynamic redirect URI
52
+ const oauthProvider = new FileOAuthProvider({
53
+ serverUrl,
54
+ redirectUri,
55
+ clientName,
56
+ tokens: existingState?.tokens,
57
+ clientInformation: existingState?.clientInformation,
58
+ codeVerifier: existingState?.codeVerifier,
59
+ });
60
+
61
+ // Start the OAuth flow - this will trigger dynamic client registration
62
+ // and set the authorization URL on the provider
63
+ const authResult = await auth(oauthProvider, { serverUrl });
64
+
65
+ if (authResult !== "REDIRECT") {
66
+ // Auth succeeded without redirect (had valid tokens)
67
+ close();
68
+ return {
69
+ success: true,
70
+ state: oauthProvider.getState(),
71
+ };
72
+ }
73
+
74
+ // Get the authorization URL
75
+ const authUrl = oauthProvider.redirectStartAuthUrl;
76
+ if (!authUrl) {
77
+ close();
78
+ return {
79
+ success: false,
80
+ error: "No authorization URL returned from OAuth flow",
81
+ };
82
+ }
83
+
84
+ // Open browser or call custom handler
85
+ const authUrlString = authUrl.toString();
86
+ if (onAuthUrl) {
87
+ onAuthUrl(authUrlString);
88
+ } else {
89
+ await openBrowser(authUrlString);
90
+ }
91
+
92
+ // Wait for the callback
93
+ const callback = await waitForCallback();
94
+
95
+ // Complete the OAuth flow by exchanging the code for tokens
96
+ const finalResult = await auth(oauthProvider, {
97
+ serverUrl,
98
+ authorizationCode: callback.code,
99
+ });
100
+
101
+ if (finalResult === "REDIRECT") {
102
+ return {
103
+ success: false,
104
+ error: "Unexpected redirect after code exchange",
105
+ };
106
+ }
107
+
108
+ return {
109
+ success: true,
110
+ state: oauthProvider.getState(),
111
+ };
112
+ } catch (err) {
113
+ close();
114
+ return {
115
+ success: false,
116
+ error: err instanceof Error ? err.message : String(err),
117
+ };
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Check if an error indicates authentication is required.
123
+ * Internal function used by addMcpCommands.
124
+ */
125
+ export function isAuthRequiredError(err: unknown): boolean {
126
+ if (!(err instanceof Error)) {
127
+ return false;
128
+ }
129
+ const message = err.message.toLowerCase();
130
+ return (
131
+ message.includes("401") ||
132
+ message.includes("unauthorized") ||
133
+ message.includes("authentication required") ||
134
+ message.includes("not authenticated") ||
135
+ message.includes("invalid_token") ||
136
+ message.includes("missing or invalid access token")
137
+ );
138
+ }