gaunt-sloth-assistant 0.7.3 → 0.8.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.
@@ -13,3 +13,21 @@ export async function configure() {
13
13
  ]
14
14
  };
15
15
  }
16
+
17
+ /*
18
+ "jira": {
19
+ "url": "https://mcp.atlassian.com/v1/sse",
20
+ "authProvider": "OAuth",
21
+ "transport": "sse"
22
+ }
23
+ */
24
+
25
+ /*
26
+ "jira": {
27
+ "command": "npx",
28
+ "args": [
29
+ "mcp-remote",
30
+ "https://mcp.atlassian.com/v1/sse"
31
+ ]
32
+ }
33
+ */
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Gaunt Sloth Assistant
2
- [![Tests and Lint](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml) [![Integration Tests (Groq)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/it.yml/badge.svg?event=push)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/it.yml)
2
+ [![Tests and Lint](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml) [![Integration Tests (Anthropic)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/it.yml/badge.svg?event=push)](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/it.yml)
3
3
 
4
4
  Gaunt GSloth Assistant is a lightweight **command line AI assistant**
5
5
  built with TypeScript (JavaScript) and distributed via NPM with minimum dependencies.
@@ -151,6 +151,13 @@ It is recommended to obtain API key from DeepSeek official website rather than f
151
151
  ### Other AI providers
152
152
  Any other AI provider supported by Langchain.js can be configured with js [Config](./docs/CONFIGURATION.md).
153
153
 
154
+ ## MCP (Model Context Protocol) Servers
155
+
156
+ Gaunt Sloth supports connecting to MCP servers, including those requiring OAuth authentication.
157
+
158
+ This has been tested with the Atlassian Jira MCP server.
159
+ See the [MCP configuration section](./docs/CONFIGURATION.md#model-context-protocol-mcp) for detailed setup instructions.
160
+
154
161
  ## Contributing
155
162
  Contributors are needed! Feel free to create a PR.
156
163
  If you are not sure where to start, look for issues with a "good first issue" label.
@@ -0,0 +1,13 @@
1
+ # v0.8.0 OAuth Support for MCP Servers
2
+
3
+ ## New Features
4
+
5
+ ### OAuth Authentication for MCP Servers
6
+ Gaunt Sloth Assistant now supports OAuth authentication for MCP (Model Context Protocol) servers!
7
+ This feature enables secure connections to remote MCP servers that require OAuth authentication,
8
+ such as the Atlassian Jira MCP server.
9
+
10
+ - OAuth tokens are stored in JSON files under `~/.gsloth/.gsloth-auth/`
11
+ - Each server's tokens are stored in a separate file named after the server URL
12
+ - Support for authorization code flow with PKCE
13
+ erver and provides a foundation for connecting to any OAuth-enabled MCP server.
@@ -1,3 +1,6 @@
1
+ export declare const GSLOTH_DIR = ".gsloth";
2
+ export declare const GSLOTH_SETTINGS_DIR = ".gsloth-settings";
3
+ export declare const GSLOTH_AUTH = ".gsloth-auth";
1
4
  export declare const USER_PROJECT_CONFIG_JS = ".gsloth.config.js";
2
5
  export declare const USER_PROJECT_CONFIG_JSON = ".gsloth.config.json";
3
6
  export declare const USER_PROJECT_CONFIG_MJS = ".gsloth.config.mjs";
package/dist/constants.js CHANGED
@@ -1,3 +1,6 @@
1
+ export const GSLOTH_DIR = '.gsloth';
2
+ export const GSLOTH_SETTINGS_DIR = '.gsloth-settings';
3
+ export const GSLOTH_AUTH = '.gsloth-auth';
1
4
  export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
2
5
  export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
3
6
  export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC"}
@@ -23,5 +23,5 @@ export declare class Invocation {
23
23
  */
24
24
  private extractAndFlattenTools;
25
25
  protected getDefaultMcpServers(): Record<string, Connection>;
26
- protected getMcpClient(config: SlothConfig): MultiServerMCPClient | null;
26
+ protected getMcpClient(config: SlothConfig): Promise<MultiServerMCPClient | null>;
27
27
  }
@@ -3,6 +3,8 @@ import { MultiServerMCPClient } from '@langchain/mcp-adapters';
3
3
  import { createReactAgent } from '@langchain/langgraph/prebuilt';
4
4
  import { formatToolCalls, ProgressIndicator } from '#src/utils.js';
5
5
  import { getDefaultTools } from '#src/builtInToolsConfig.js';
6
+ import { createAuthProviderAndAuthenticate } from '#src/mcp/OAuthClientProviderImpl.js';
7
+ import { displayInfo } from '#src/consoleUtils.js';
6
8
  export class Invocation {
7
9
  statusUpdate;
8
10
  verbose = false;
@@ -22,7 +24,7 @@ export class Invocation {
22
24
  }
23
25
  // Merge command-specific filesystem config if provided
24
26
  this.config = this.getEffectiveConfig(config, command);
25
- this.mcpClient = this.getMcpClient(this.config);
27
+ this.mcpClient = await this.getMcpClient(this.config);
26
28
  // Get default filesystem tools (filtered based on config)
27
29
  const defaultTools = await getDefaultTools(config);
28
30
  // Get user config tools
@@ -144,20 +146,33 @@ export class Invocation {
144
146
  getDefaultMcpServers() {
145
147
  return {};
146
148
  }
147
- getMcpClient(config) {
149
+ async getMcpClient(config) {
148
150
  const defaultServers = this.getDefaultMcpServers();
149
151
  // Merge with user's mcpServers
150
- const mcpServers = { ...defaultServers, ...(config.mcpServers || {}) };
151
- // If user provided their own filesystem config, it overrides default
152
- if (config.mcpServers?.filesystem) {
153
- mcpServers.filesystem = config.mcpServers.filesystem;
152
+ const rawMcpServers = { ...defaultServers, ...(config.mcpServers || {}) };
153
+ const mcpServers = {};
154
+ for (const serverName of Object.keys(rawMcpServers)) {
155
+ const server = rawMcpServers[serverName];
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ if (server.url && server && server.authProvider === 'OAuth') {
158
+ displayInfo(`Starting OAuth for for ${server.url}`);
159
+ const authProvider = await createAuthProviderAndAuthenticate(server);
160
+ mcpServers[serverName] = {
161
+ ...server,
162
+ authProvider,
163
+ };
164
+ }
165
+ else {
166
+ // Add non-OAuth servers as-is
167
+ mcpServers[serverName] = server;
168
+ }
154
169
  }
155
170
  if (Object.keys(mcpServers).length > 0) {
156
171
  return new MultiServerMCPClient({
157
172
  throwOnLoadError: true,
158
173
  prefixToolNameWithServerName: true,
159
174
  additionalToolNamePrefix: 'mcp',
160
- mcpServers,
175
+ mcpServers: mcpServers,
161
176
  });
162
177
  }
163
178
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"Invocation.js","sourceRoot":"","sources":["../../src/core/Invocation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Invocation.js","sourceRoot":"","sources":["../../src/core/Invocation.ts"],"names":[],"mappings":"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"}
@@ -2,8 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { mkdirSync } from 'node:fs';
4
4
  import { getCurrentDir } from '#src/systemUtils.js';
5
- const GSLOTH_DIR = '.gsloth';
6
- const GSLOTH_SETTINGS_DIR = '.gsloth-settings';
5
+ import { GSLOTH_DIR, GSLOTH_SETTINGS_DIR } from '#src/constants.js';
7
6
  /**
8
7
  * Checks if .gsloth directory exists in the project root
9
8
  * @returns Boolean indicating whether .gsloth directory exists
@@ -1 +1 @@
1
- {"version":3,"file":"filePathUtils.js","sourceRoot":"","sources":["../src/filePathUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,UAAU,GAAG,SAAS,CAAC;AAC7B,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,MAAM,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;QAEvE,wDAAwD;QACxD,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACpC,SAAS,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,OAAO,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,MAAM,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAEzD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"filePathUtils.js","sourceRoot":"","sources":["../src/filePathUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,MAAM,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;QAEvE,wDAAwD;QACxD,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACpC,SAAS,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,OAAO,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,MAAM,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAEzD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Gets the global .gsloth directory path in the user's home directory
3
+ * @returns The resolved path to the global .gsloth directory
4
+ */
5
+ export declare function getGlobalGslothDir(): string;
6
+ /**
7
+ * Ensures the global .gsloth directory exists in the user's home directory
8
+ * Creates it if it doesn't exist
9
+ * @returns The resolved path to the global .gsloth directory
10
+ */
11
+ export declare function ensureGlobalGslothDir(): string;
12
+ /**
13
+ * Gets the global auth directory path
14
+ * @returns The resolved path to the global auth directory
15
+ */
16
+ export declare function getGlobalAuthDir(): string;
17
+ /**
18
+ * Ensures the global auth directory exists
19
+ * Creates it if it doesn't exist
20
+ * @returns The resolved path to the global auth directory
21
+ */
22
+ export declare function ensureGlobalAuthDir(): string;
23
+ /**
24
+ * Gets the path for a specific OAuth provider's storage file
25
+ * @param serverUrl The server URL or identifier for the OAuth provider
26
+ * @returns The resolved path where the OAuth data should be stored
27
+ */
28
+ export declare function getOAuthStoragePath(serverUrl: string): string;
@@ -0,0 +1,61 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { GSLOTH_DIR, GSLOTH_AUTH } from '#src/constants.js';
5
+ /**
6
+ * Gets the global .gsloth directory path in the user's home directory
7
+ * @returns The resolved path to the global .gsloth directory
8
+ */
9
+ export function getGlobalGslothDir() {
10
+ return resolve(homedir(), GSLOTH_DIR);
11
+ }
12
+ /**
13
+ * Ensures the global .gsloth directory exists in the user's home directory
14
+ * Creates it if it doesn't exist
15
+ * @returns The resolved path to the global .gsloth directory
16
+ */
17
+ export function ensureGlobalGslothDir() {
18
+ const globalDir = getGlobalGslothDir();
19
+ if (!existsSync(globalDir)) {
20
+ mkdirSync(globalDir, { recursive: true });
21
+ }
22
+ return globalDir;
23
+ }
24
+ /**
25
+ * Gets the global auth directory path
26
+ * @returns The resolved path to the global auth directory
27
+ */
28
+ export function getGlobalAuthDir() {
29
+ const globalDir = getGlobalGslothDir();
30
+ return resolve(globalDir, GSLOTH_AUTH);
31
+ }
32
+ /**
33
+ * Ensures the global auth directory exists
34
+ * Creates it if it doesn't exist
35
+ * @returns The resolved path to the global auth directory
36
+ */
37
+ export function ensureGlobalAuthDir() {
38
+ // First ensure parent directory exists
39
+ ensureGlobalGslothDir();
40
+ const authDir = getGlobalAuthDir();
41
+ if (!existsSync(authDir)) {
42
+ mkdirSync(authDir, { recursive: true });
43
+ }
44
+ return authDir;
45
+ }
46
+ /**
47
+ * Gets the path for a specific OAuth provider's storage file
48
+ * @param serverUrl The server URL or identifier for the OAuth provider
49
+ * @returns The resolved path where the OAuth data should be stored
50
+ */
51
+ export function getOAuthStoragePath(serverUrl) {
52
+ const authDir = ensureGlobalAuthDir();
53
+ // Create a safe filename from the server URL
54
+ const safeFilename = serverUrl
55
+ .replace(/https?:\/\//, '')
56
+ .replace(/[^a-zA-Z0-9.-]/g, '_')
57
+ .replace(/_+/g, '_')
58
+ .toLowerCase();
59
+ return resolve(authDir, `${safeFilename}.json`);
60
+ }
61
+ //# sourceMappingURL=globalConfigUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalConfigUtils.js","sourceRoot":"","sources":["../src/globalConfigUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;IAEvC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;IACvC,OAAO,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,uCAAuC;IACvC,qBAAqB,EAAE,CAAC;IAExB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IAEnC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,6CAA6C;IAC7C,MAAM,YAAY,GAAG,SAAS;SAC3B,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,WAAW,EAAE,CAAC;IAEjB,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,YAAY,OAAO,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
2
+ import type { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
3
+ import { StreamableHTTPConnection } from '@langchain/mcp-adapters';
4
+ import http from 'http';
5
+ interface OAuthClientProviderConfig {
6
+ redirectUrl: string;
7
+ serverUrl: string;
8
+ }
9
+ /**
10
+ * Please note most of these "unused" methods are part of {@link OAuthClientProvider}
11
+ */
12
+ export declare class OAuthClientProviderImpl implements OAuthClientProvider {
13
+ private config;
14
+ private innerState;
15
+ private storagePath;
16
+ constructor(config: OAuthClientProviderConfig);
17
+ private loadStorageData;
18
+ private saveStorageData;
19
+ state(): string | Promise<string>;
20
+ get redirectUrl(): string;
21
+ get clientMetadata(): OAuthClientMetadata;
22
+ saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void>;
23
+ clientInformation(): Promise<OAuthClientInformationFull | undefined>;
24
+ saveTokens(tokens: OAuthTokens): Promise<void>;
25
+ tokens(): OAuthTokens | undefined;
26
+ saveCodeVerifier(codeVerifier: string): Promise<void>;
27
+ codeVerifier(): Promise<string>;
28
+ redirectToAuthorization(authUrl: URL): Promise<void>;
29
+ }
30
+ export declare function createAuthProviderAndAuthenticate(mcpServer: StreamableHTTPConnection): Promise<OAuthClientProviderImpl>;
31
+ export declare function createOAuthRedirectServer(path: string, portParam?: number): Promise<{
32
+ port: number;
33
+ server: http.Server;
34
+ codePromise: Promise<string>;
35
+ }>;
36
+ export {};
@@ -0,0 +1,197 @@
1
+ import { auth } from '@modelcontextprotocol/sdk/client/auth.js';
2
+ import express from 'express';
3
+ import * as crypto from 'crypto';
4
+ import { platform } from 'node:os';
5
+ import { execSync } from 'node:child_process';
6
+ import { displayInfo } from '#src/consoleUtils.js';
7
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
8
+ import { getOAuthStoragePath } from '#src/globalConfigUtils.js';
9
+ /**
10
+ * Please note most of these "unused" methods are part of {@link OAuthClientProvider}
11
+ */
12
+ export class OAuthClientProviderImpl {
13
+ config;
14
+ innerState;
15
+ storagePath;
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.innerState = crypto.randomUUID();
19
+ if (!this.config.redirectUrl) {
20
+ throw new Error('No redirect URL provided');
21
+ }
22
+ if (!this.config.serverUrl) {
23
+ throw new Error('No server URL provided');
24
+ }
25
+ this.storagePath = getOAuthStoragePath(this.config.serverUrl);
26
+ }
27
+ loadStorageData() {
28
+ if (existsSync(this.storagePath)) {
29
+ try {
30
+ const data = readFileSync(this.storagePath, 'utf-8');
31
+ return JSON.parse(data);
32
+ }
33
+ catch (error) {
34
+ displayInfo('Failed to load OAuth storage data:' + error);
35
+ return {};
36
+ }
37
+ }
38
+ return {};
39
+ }
40
+ saveStorageData(data) {
41
+ try {
42
+ writeFileSync(this.storagePath, JSON.stringify(data, null, 2), 'utf-8');
43
+ }
44
+ catch (error) {
45
+ console.error('Failed to save OAuth storage data:', error);
46
+ }
47
+ }
48
+ state() {
49
+ return this.innerState;
50
+ }
51
+ // noinspection JSUnusedGlobalSymbols
52
+ get redirectUrl() {
53
+ return this.config.redirectUrl;
54
+ }
55
+ // noinspection JSUnusedGlobalSymbols
56
+ get clientMetadata() {
57
+ return {
58
+ redirect_uris: [this.config.redirectUrl],
59
+ client_name: 'Gaunt Sloth Assistant',
60
+ client_uri: 'https://github.com/andruhon/gaunt-sloth-assistant',
61
+ software_id: '1dd38b83-946b-4631-8855-66ee467bfd68',
62
+ scope: 'mcp:read mcp:write',
63
+ token_endpoint_auth_method: 'none',
64
+ grant_types: ['authorization_code', 'refresh_token'],
65
+ response_types: ['code'],
66
+ };
67
+ }
68
+ // noinspection JSUnusedGlobalSymbols
69
+ saveClientInformation(clientInformation) {
70
+ const data = this.loadStorageData();
71
+ data.clientInformation = clientInformation;
72
+ this.saveStorageData(data);
73
+ return Promise.resolve();
74
+ }
75
+ // noinspection JSUnusedGlobalSymbols
76
+ async clientInformation() {
77
+ const data = this.loadStorageData();
78
+ return Promise.resolve(data.clientInformation);
79
+ }
80
+ // noinspection JSUnusedGlobalSymbols
81
+ async saveTokens(tokens) {
82
+ const data = this.loadStorageData();
83
+ data.tokens = tokens;
84
+ this.saveStorageData(data);
85
+ }
86
+ // noinspection JSUnusedGlobalSymbols
87
+ tokens() {
88
+ const data = this.loadStorageData();
89
+ return data.tokens;
90
+ }
91
+ // noinspection JSUnusedGlobalSymbols
92
+ saveCodeVerifier(codeVerifier) {
93
+ const data = this.loadStorageData();
94
+ data.codeVerifier = codeVerifier;
95
+ this.saveStorageData(data);
96
+ return Promise.resolve();
97
+ }
98
+ // noinspection JSUnusedGlobalSymbols
99
+ codeVerifier() {
100
+ const data = this.loadStorageData();
101
+ if (!data.codeVerifier) {
102
+ throw new Error('No code verifier stored');
103
+ }
104
+ return Promise.resolve(data.codeVerifier);
105
+ }
106
+ // noinspection JSUnusedGlobalSymbols
107
+ async redirectToAuthorization(authUrl) {
108
+ displayInfo('Auth url: ' + authUrl.toString());
109
+ try {
110
+ const url = authUrl.toString();
111
+ displayInfo('Trying to open browser');
112
+ // Handle different platforms
113
+ const platformName = platform();
114
+ if (platformName === 'win32') {
115
+ // Windows
116
+ execSync(`start "" "${url}"`);
117
+ }
118
+ else if (platformName === 'darwin') {
119
+ // macOS
120
+ execSync(`open "${url}"`);
121
+ }
122
+ else {
123
+ // Linux and others
124
+ execSync(`xdg-open "${url}"`);
125
+ }
126
+ }
127
+ catch (error) {
128
+ displayInfo(`Failed to open browser: ${error}`);
129
+ displayInfo(`Please open ${authUrl.toString()} in your browser`);
130
+ }
131
+ }
132
+ }
133
+ export async function createAuthProviderAndAuthenticate(mcpServer) {
134
+ const { port, server, codePromise } = await createOAuthRedirectServer('/oauth-callback');
135
+ const authProvider = new OAuthClientProviderImpl({
136
+ redirectUrl: `http://127.0.0.1:${port}/oauth-callback`,
137
+ serverUrl: mcpServer.url,
138
+ });
139
+ const outcome = await auth(authProvider, { serverUrl: mcpServer.url });
140
+ if (outcome == 'REDIRECT') {
141
+ const authorizationCode = await codePromise;
142
+ await auth(authProvider, { serverUrl: mcpServer.url, authorizationCode });
143
+ }
144
+ else if (outcome == 'AUTHORIZED') {
145
+ try {
146
+ server.close();
147
+ }
148
+ catch { }
149
+ displayInfo('Authorized');
150
+ }
151
+ else {
152
+ throw new Error(`Unexpected Auth outcome: ${outcome}`);
153
+ }
154
+ return authProvider;
155
+ }
156
+ export function createOAuthRedirectServer(path, portParam = 0) {
157
+ const redirectApp = express();
158
+ return new Promise((resolve, reject) => {
159
+ const codePromise = new Promise((resolveCode, rejectCode) => {
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ redirectApp.get(path, (req, res) => {
162
+ const code = req.query.code;
163
+ if (!code) {
164
+ res.status(400).send('Error: No auth code received');
165
+ rejectCode('Error: No auth code received');
166
+ return;
167
+ }
168
+ res.send(`<div style="height: 80vh;text-align: center;display: flex;justify-content: center;align-items: center;">
169
+ <div>
170
+ <h1>Auth successful!</h1>
171
+ You may close this window and return to the Gaunt Sloth.
172
+ <div>
173
+ <img src="data:image/bmp;base64,Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAnD98ZBtDTQ8sey9YOQobRA8nZx9OdyxahzZrMwERdCtShjZqLQUVLwgZKwUWKQYXsFCRq06KciJPnEh/n0GEmjuClDl9lzl/uFGglz56dChOm0h0SBInLgQVKwgZKgUVgzZchDRfqEeLsUmavFWjvV6joEGFizFvsU+Yw2CowWCiu1qdbCRNKgEQMAofMAcebSRIgC9bt1KdqEqNnD6BokGJqEaOvVmjqEWMu12ftFeYwGCjpUOMUxc8KQIRLAQWhjVmo0qGs1CYqEmOqEmPmjyArk2UrEyQr02TsU2WwV6pvF2eo0CKlDl+OgsiJwQSijZthS9qnDuAo0GFqESMmz2BijZtpUOLnT2CuVicyWmtsVCUjDh0p0KQbiVYJAQSkzx3kzZ7iS5rv2mn3Y3Gq0+Pgi1laCFOfyxmmTx/slCVmT55eihemzyGjTd6NwgfgSxokzl9qkuY4pLR+Lvg1H3EjTRzZB1IjTV3cCNWmjmAgC1diCxpjC1ulDd8RhAtdihbljl/vmK1v2WuuWGgtVWdjDVwgS5ldCVZcydckjl5kzh3ynGwxHOooT6DSA8sQQwkpUeTyGrBlTp8eCNZkzh1rEeSmDx9ZB9JcyNYkzh2tlek5pjT8q3bqU2PPQokJgEPhTVxy2nBkDd6kDV2qEaOwGOvvWGxuViqoUSNlDd6nj6FvmKq13zKnkiJMgUZKwUWPg4poUaOkzp+kTh7nkCExG60033Jym7C03zKsVOdiC9tjjR10W7IeThrKAAOMAUYJgUULgUZdCZdqkqYu1ersk+fvFyww2q4z3fDqUmXhy5ulTp/yWi/Txo6JgEPLgYZLQUXKQUVJwkaSxExq1GTxWW3t1SpsU+gv1mvnUOLjTJ3izh3dTRkJwEQKgQWLQYaLAgcLQUYLQYZLAERezZaiTtmXyFJgjp0ijt1iTlqOAsjNQ0pKQASJwUVKQYYLgUZLAQZLAgcLgUZMAUYZyVRfCthPQoiIQAJOgcgYyBLKAIRLAUZNwsoMQcfKAQV"
174
+ width="16" height="16">
175
+ </div>
176
+ </div>
177
+ </div>`);
178
+ resolveCode(code);
179
+ if (server) {
180
+ displayInfo('Cleaning auth redirect server...');
181
+ server.close();
182
+ }
183
+ });
184
+ });
185
+ const server = redirectApp.listen(portParam, () => {
186
+ const addressInfo = server.address();
187
+ const port = addressInfo.port;
188
+ displayInfo(`OAuth callback server listening at ${port}`);
189
+ resolve({ port, server, codePromise });
190
+ });
191
+ server.on('error', (err) => {
192
+ // If the server fails to start, reject the outer promise.
193
+ reject(err);
194
+ });
195
+ });
196
+ }
197
+ //# sourceMappingURL=OAuthClientProviderImpl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthClientProviderImpl.js","sourceRoot":"","sources":["../../src/mcp/OAuthClientProviderImpl.ts"],"names":[],"mappings":"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"}
@@ -261,8 +261,43 @@ See [Langchain documentation](https://js.langchain.com/docs/tutorials/llm_chain/
261
261
 
262
262
  ## Model Context Protocol (MCP)
263
263
 
264
- Gaunt Sloth Assistant supports the Model Context Protocol (MCP), which provides enhanced context management.
265
- The `@modelcontextprotocol/server-filesystem` package is included as a dependency, allowing you to easily configure file system access for your LLM.
264
+ Gaunt Sloth Assistant supports the Model Context Protocol (MCP), which provides enhanced context management. You can connect to various MCP servers, including those requiring OAuth authentication.
265
+
266
+ ### OAuth-enabled MCP Servers
267
+
268
+ Gaunt Sloth now supports OAuth authentication for MCP servers. This has been tested with the Atlassian Jira MCP server.
269
+
270
+ #### Example: Atlassian Jira MCP Server
271
+
272
+ To connect to the Atlassian Jira MCP server using OAuth, add the following to your `.gsloth.config.json`:
273
+
274
+ ```json
275
+ {
276
+ "llm": {
277
+ "type": "vertexai",
278
+ "model": "gemini-2.5-pro",
279
+ "temperature": 0
280
+ },
281
+ "mcpServers": {
282
+ "jira": {
283
+ "url": "https://mcp.atlassian.com/v1/sse",
284
+ "authProvider": "OAuth",
285
+ "transport": "sse"
286
+ }
287
+ }
288
+ }
289
+ ```
290
+
291
+ **OAuth Authentication Flow:**
292
+ 1. When you first use a command that requires the MCP server, your browser will open automatically
293
+ 2. Complete the OAuth authentication in your browser
294
+ 3. The authentication tokens are stored securely in `~/.gsloth/.gsloth-auth/`
295
+ 4. Future sessions will use the stored tokens automatically
296
+
297
+ **Token Storage:**
298
+ - OAuth tokens are stored in JSON files under `~/.gsloth/.gsloth-auth/`
299
+ - Each server's tokens are stored in a separate file named after the server URL
300
+ - The storage location is cross-platform (Windows, macOS, Linux)
266
301
 
267
302
  ### MCP Filesystem Server Configuration
268
303
 
package/eslint.config.js CHANGED
@@ -13,7 +13,13 @@ const __filename = fileURLToPath(import.meta.url);
13
13
  const __dirname = path.dirname(__filename);
14
14
 
15
15
  // Global ignores applied to all configurations
16
- const globalIgnores = ['**/node_modules/**', '**/dist/**', 'coverage/**', '.git/**'];
16
+ const globalIgnores = [
17
+ '**/node_modules/**',
18
+ '**/dist/**',
19
+ '**/test-data/**',
20
+ 'coverage/**',
21
+ '.git/**',
22
+ ];
17
23
 
18
24
  export default defineConfig([
19
25
  // Ignore files config - applies first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaunt-sloth-assistant",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "author": "Andrew Kondratev",
@@ -34,11 +34,13 @@
34
34
  "chalk": "^5.4.1",
35
35
  "commander": "^14.0.0",
36
36
  "diff": "^8.0.2",
37
+ "express": "^5.1.0",
37
38
  "minimatch": "^10.0.3"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@eslint/js": "^9.26.0",
41
42
  "@types/diff": "^7.0.2",
43
+ "@types/express": "^5.0.3",
42
44
  "@types/node": "^24.0.7",
43
45
  "@types/uuid": "^10.0.0",
44
46
  "@typescript-eslint/eslint-plugin": "^8.35.0",
package/src/constants.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export const GSLOTH_DIR = '.gsloth';
2
+ export const GSLOTH_SETTINGS_DIR = '.gsloth-settings';
3
+ export const GSLOTH_AUTH = '.gsloth-auth';
1
4
  export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
2
5
  export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
3
6
  export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
@@ -2,7 +2,7 @@ import type { Message } from '#src/modules/types.js';
2
2
  import { AIMessage, isAIMessage } from '@langchain/core/messages';
3
3
  import { SlothConfig } from '#src/config.js';
4
4
  import type { Connection } from '@langchain/mcp-adapters';
5
- import { MultiServerMCPClient } from '@langchain/mcp-adapters';
5
+ import { MultiServerMCPClient, StreamableHTTPConnection } from '@langchain/mcp-adapters';
6
6
  import { createReactAgent } from '@langchain/langgraph/prebuilt';
7
7
  import { BaseCheckpointSaver, CompiledStateGraph } from '@langchain/langgraph';
8
8
  import { formatToolCalls, ProgressIndicator } from '#src/utils.js';
@@ -11,6 +11,8 @@ import { ToolCall } from '@langchain/core/messages/tool';
11
11
  import { GthCommand, StatusLevel } from '#src/core/types.js';
12
12
  import { BaseToolkit, StructuredToolInterface } from '@langchain/core/tools';
13
13
  import { getDefaultTools } from '#src/builtInToolsConfig.js';
14
+ import { createAuthProviderAndAuthenticate } from '#src/mcp/OAuthClientProviderImpl.js';
15
+ import { displayInfo } from '#src/consoleUtils.js';
14
16
 
15
17
  export type StatusUpdateCallback = (level: StatusLevel, message: string) => void;
16
18
 
@@ -41,7 +43,7 @@ export class Invocation {
41
43
 
42
44
  // Merge command-specific filesystem config if provided
43
45
  this.config = this.getEffectiveConfig(config, command);
44
- this.mcpClient = this.getMcpClient(this.config);
46
+ this.mcpClient = await this.getMcpClient(this.config);
45
47
 
46
48
  // Get default filesystem tools (filtered based on config)
47
49
  const defaultTools = await getDefaultTools(config);
@@ -180,15 +182,27 @@ export class Invocation {
180
182
  return {};
181
183
  }
182
184
 
183
- protected getMcpClient(config: SlothConfig) {
185
+ protected async getMcpClient(config: SlothConfig) {
184
186
  const defaultServers = this.getDefaultMcpServers();
185
187
 
186
188
  // Merge with user's mcpServers
187
- const mcpServers = { ...defaultServers, ...(config.mcpServers || {}) };
188
-
189
- // If user provided their own filesystem config, it overrides default
190
- if (config.mcpServers?.filesystem) {
191
- mcpServers.filesystem = config.mcpServers.filesystem;
189
+ const rawMcpServers = { ...defaultServers, ...(config.mcpServers || {}) };
190
+
191
+ const mcpServers = {} as Record<string, StreamableHTTPConnection>;
192
+ for (const serverName of Object.keys(rawMcpServers)) {
193
+ const server = rawMcpServers[serverName] as StreamableHTTPConnection;
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
+ if (server.url && server && (server.authProvider as any) === 'OAuth') {
196
+ displayInfo(`Starting OAuth for for ${server.url}`);
197
+ const authProvider = await createAuthProviderAndAuthenticate(server);
198
+ mcpServers[serverName] = {
199
+ ...server,
200
+ authProvider,
201
+ };
202
+ } else {
203
+ // Add non-OAuth servers as-is
204
+ mcpServers[serverName] = server;
205
+ }
192
206
  }
193
207
 
194
208
  if (Object.keys(mcpServers).length > 0) {
@@ -196,7 +210,7 @@ export class Invocation {
196
210
  throwOnLoadError: true,
197
211
  prefixToolNameWithServerName: true,
198
212
  additionalToolNamePrefix: 'mcp',
199
- mcpServers,
213
+ mcpServers: mcpServers,
200
214
  });
201
215
  } else {
202
216
  return null;
@@ -2,9 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { mkdirSync } from 'node:fs';
4
4
  import { getCurrentDir } from '#src/systemUtils.js';
5
-
6
- const GSLOTH_DIR = '.gsloth';
7
- const GSLOTH_SETTINGS_DIR = '.gsloth-settings';
5
+ import { GSLOTH_DIR, GSLOTH_SETTINGS_DIR } from '#src/constants.js';
8
6
 
9
7
  /**
10
8
  * Checks if .gsloth directory exists in the project root
@@ -0,0 +1,71 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { GSLOTH_DIR, GSLOTH_AUTH } from '#src/constants.js';
5
+
6
+ /**
7
+ * Gets the global .gsloth directory path in the user's home directory
8
+ * @returns The resolved path to the global .gsloth directory
9
+ */
10
+ export function getGlobalGslothDir(): string {
11
+ return resolve(homedir(), GSLOTH_DIR);
12
+ }
13
+
14
+ /**
15
+ * Ensures the global .gsloth directory exists in the user's home directory
16
+ * Creates it if it doesn't exist
17
+ * @returns The resolved path to the global .gsloth directory
18
+ */
19
+ export function ensureGlobalGslothDir(): string {
20
+ const globalDir = getGlobalGslothDir();
21
+
22
+ if (!existsSync(globalDir)) {
23
+ mkdirSync(globalDir, { recursive: true });
24
+ }
25
+
26
+ return globalDir;
27
+ }
28
+
29
+ /**
30
+ * Gets the global auth directory path
31
+ * @returns The resolved path to the global auth directory
32
+ */
33
+ export function getGlobalAuthDir(): string {
34
+ const globalDir = getGlobalGslothDir();
35
+ return resolve(globalDir, GSLOTH_AUTH);
36
+ }
37
+
38
+ /**
39
+ * Ensures the global auth directory exists
40
+ * Creates it if it doesn't exist
41
+ * @returns The resolved path to the global auth directory
42
+ */
43
+ export function ensureGlobalAuthDir(): string {
44
+ // First ensure parent directory exists
45
+ ensureGlobalGslothDir();
46
+
47
+ const authDir = getGlobalAuthDir();
48
+
49
+ if (!existsSync(authDir)) {
50
+ mkdirSync(authDir, { recursive: true });
51
+ }
52
+
53
+ return authDir;
54
+ }
55
+
56
+ /**
57
+ * Gets the path for a specific OAuth provider's storage file
58
+ * @param serverUrl The server URL or identifier for the OAuth provider
59
+ * @returns The resolved path where the OAuth data should be stored
60
+ */
61
+ export function getOAuthStoragePath(serverUrl: string): string {
62
+ const authDir = ensureGlobalAuthDir();
63
+ // Create a safe filename from the server URL
64
+ const safeFilename = serverUrl
65
+ .replace(/https?:\/\//, '')
66
+ .replace(/[^a-zA-Z0-9.-]/g, '_')
67
+ .replace(/_+/g, '_')
68
+ .toLowerCase();
69
+
70
+ return resolve(authDir, `${safeFilename}.json`);
71
+ }
@@ -0,0 +1,236 @@
1
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
2
+ import { auth } from '@modelcontextprotocol/sdk/client/auth.js';
3
+ import type {
4
+ OAuthClientInformationFull,
5
+ OAuthClientMetadata,
6
+ OAuthTokens,
7
+ } from '@modelcontextprotocol/sdk/shared/auth.js';
8
+ import express from 'express';
9
+ import * as crypto from 'crypto';
10
+ import { platform } from 'node:os';
11
+ import { execSync } from 'node:child_process';
12
+ import { AddressInfo } from 'net';
13
+ import { displayInfo } from '#src/consoleUtils.js';
14
+ import { StreamableHTTPConnection } from '@langchain/mcp-adapters';
15
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
16
+ import { getOAuthStoragePath } from '#src/globalConfigUtils.js';
17
+ import http from 'http';
18
+
19
+ interface OAuthClientProviderConfig {
20
+ redirectUrl: string;
21
+ serverUrl: string;
22
+ }
23
+
24
+ interface OAuthStorageData {
25
+ tokens?: OAuthTokens;
26
+ codeVerifier?: string;
27
+ clientInformation?: OAuthClientInformationFull;
28
+ }
29
+
30
+ /**
31
+ * Please note most of these "unused" methods are part of {@link OAuthClientProvider}
32
+ */
33
+ export class OAuthClientProviderImpl implements OAuthClientProvider {
34
+ private config: OAuthClientProviderConfig;
35
+ private innerState: string;
36
+ private storagePath: string;
37
+
38
+ constructor(config: OAuthClientProviderConfig) {
39
+ this.config = config as OAuthClientProviderConfig;
40
+ this.innerState = crypto.randomUUID();
41
+ if (!this.config.redirectUrl) {
42
+ throw new Error('No redirect URL provided');
43
+ }
44
+ if (!this.config.serverUrl) {
45
+ throw new Error('No server URL provided');
46
+ }
47
+ this.storagePath = getOAuthStoragePath(this.config.serverUrl);
48
+ }
49
+
50
+ private loadStorageData(): OAuthStorageData {
51
+ if (existsSync(this.storagePath)) {
52
+ try {
53
+ const data = readFileSync(this.storagePath, 'utf-8');
54
+ return JSON.parse(data);
55
+ } catch (error) {
56
+ displayInfo('Failed to load OAuth storage data:' + error);
57
+ return {};
58
+ }
59
+ }
60
+ return {};
61
+ }
62
+
63
+ private saveStorageData(data: OAuthStorageData): void {
64
+ try {
65
+ writeFileSync(this.storagePath, JSON.stringify(data, null, 2), 'utf-8');
66
+ } catch (error) {
67
+ console.error('Failed to save OAuth storage data:', error);
68
+ }
69
+ }
70
+
71
+ state(): string | Promise<string> {
72
+ return this.innerState;
73
+ }
74
+
75
+ // noinspection JSUnusedGlobalSymbols
76
+ get redirectUrl() {
77
+ return this.config.redirectUrl;
78
+ }
79
+
80
+ // noinspection JSUnusedGlobalSymbols
81
+ get clientMetadata(): OAuthClientMetadata {
82
+ return {
83
+ redirect_uris: [this.config.redirectUrl],
84
+ client_name: 'Gaunt Sloth Assistant',
85
+ client_uri: 'https://github.com/andruhon/gaunt-sloth-assistant',
86
+ software_id: '1dd38b83-946b-4631-8855-66ee467bfd68',
87
+ scope: 'mcp:read mcp:write',
88
+ token_endpoint_auth_method: 'none',
89
+ grant_types: ['authorization_code', 'refresh_token'],
90
+ response_types: ['code'],
91
+ };
92
+ }
93
+
94
+ // noinspection JSUnusedGlobalSymbols
95
+ saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void> {
96
+ const data = this.loadStorageData();
97
+ data.clientInformation = clientInformation;
98
+ this.saveStorageData(data);
99
+ return Promise.resolve();
100
+ }
101
+
102
+ // noinspection JSUnusedGlobalSymbols
103
+ async clientInformation(): Promise<OAuthClientInformationFull | undefined> {
104
+ const data = this.loadStorageData();
105
+ return Promise.resolve(data.clientInformation);
106
+ }
107
+
108
+ // noinspection JSUnusedGlobalSymbols
109
+ async saveTokens(tokens: OAuthTokens): Promise<void> {
110
+ const data = this.loadStorageData();
111
+ data.tokens = tokens;
112
+ this.saveStorageData(data);
113
+ }
114
+
115
+ // noinspection JSUnusedGlobalSymbols
116
+ tokens(): OAuthTokens | undefined {
117
+ const data = this.loadStorageData();
118
+ return data.tokens;
119
+ }
120
+
121
+ // noinspection JSUnusedGlobalSymbols
122
+ saveCodeVerifier(codeVerifier: string): Promise<void> {
123
+ const data = this.loadStorageData();
124
+ data.codeVerifier = codeVerifier;
125
+ this.saveStorageData(data);
126
+ return Promise.resolve();
127
+ }
128
+
129
+ // noinspection JSUnusedGlobalSymbols
130
+ codeVerifier(): Promise<string> {
131
+ const data = this.loadStorageData();
132
+ if (!data.codeVerifier) {
133
+ throw new Error('No code verifier stored');
134
+ }
135
+ return Promise.resolve(data.codeVerifier);
136
+ }
137
+
138
+ // noinspection JSUnusedGlobalSymbols
139
+ async redirectToAuthorization(authUrl: URL): Promise<void> {
140
+ displayInfo('Auth url: ' + authUrl.toString());
141
+ try {
142
+ const url = authUrl.toString();
143
+ displayInfo('Trying to open browser');
144
+
145
+ // Handle different platforms
146
+ const platformName = platform();
147
+ if (platformName === 'win32') {
148
+ // Windows
149
+ execSync(`start "" "${url}"`);
150
+ } else if (platformName === 'darwin') {
151
+ // macOS
152
+ execSync(`open "${url}"`);
153
+ } else {
154
+ // Linux and others
155
+ execSync(`xdg-open "${url}"`);
156
+ }
157
+ } catch (error) {
158
+ displayInfo(`Failed to open browser: ${error}`);
159
+ displayInfo(`Please open ${authUrl.toString()} in your browser`);
160
+ }
161
+ }
162
+ }
163
+
164
+ export async function createAuthProviderAndAuthenticate(
165
+ mcpServer: StreamableHTTPConnection
166
+ ): Promise<OAuthClientProviderImpl> {
167
+ const { port, server, codePromise } = await createOAuthRedirectServer('/oauth-callback');
168
+ const authProvider = new OAuthClientProviderImpl({
169
+ redirectUrl: `http://127.0.0.1:${port}/oauth-callback`,
170
+ serverUrl: mcpServer.url,
171
+ });
172
+ const outcome = await auth(authProvider, { serverUrl: mcpServer.url });
173
+ if (outcome == 'REDIRECT') {
174
+ const authorizationCode = await codePromise;
175
+ await auth(authProvider, { serverUrl: mcpServer.url, authorizationCode });
176
+ } else if (outcome == 'AUTHORIZED') {
177
+ try {
178
+ server.close();
179
+ } catch {}
180
+ displayInfo('Authorized');
181
+ } else {
182
+ throw new Error(`Unexpected Auth outcome: ${outcome}`);
183
+ }
184
+ return authProvider;
185
+ }
186
+
187
+ export function createOAuthRedirectServer(
188
+ path: string,
189
+ portParam: number = 0
190
+ ): Promise<{
191
+ port: number;
192
+ server: http.Server;
193
+ codePromise: Promise<string>;
194
+ }> {
195
+ const redirectApp = express();
196
+ return new Promise((resolve, reject) => {
197
+ const codePromise = new Promise<string>((resolveCode, rejectCode) => {
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ redirectApp.get(path, (req: any, res: any) => {
200
+ const code = req.query.code as string | undefined;
201
+ if (!code) {
202
+ res.status(400).send('Error: No auth code received');
203
+ rejectCode('Error: No auth code received');
204
+ return;
205
+ }
206
+ res.send(`<div style="height: 80vh;text-align: center;display: flex;justify-content: center;align-items: center;">
207
+ <div>
208
+ <h1>Auth successful!</h1>
209
+ You may close this window and return to the Gaunt Sloth.
210
+ <div>
211
+ <img src="data:image/bmp;base64,Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAnD98ZBtDTQ8sey9YOQobRA8nZx9OdyxahzZrMwERdCtShjZqLQUVLwgZKwUWKQYXsFCRq06KciJPnEh/n0GEmjuClDl9lzl/uFGglz56dChOm0h0SBInLgQVKwgZKgUVgzZchDRfqEeLsUmavFWjvV6joEGFizFvsU+Yw2CowWCiu1qdbCRNKgEQMAofMAcebSRIgC9bt1KdqEqNnD6BokGJqEaOvVmjqEWMu12ftFeYwGCjpUOMUxc8KQIRLAQWhjVmo0qGs1CYqEmOqEmPmjyArk2UrEyQr02TsU2WwV6pvF2eo0CKlDl+OgsiJwQSijZthS9qnDuAo0GFqESMmz2BijZtpUOLnT2CuVicyWmtsVCUjDh0p0KQbiVYJAQSkzx3kzZ7iS5rv2mn3Y3Gq0+Pgi1laCFOfyxmmTx/slCVmT55eihemzyGjTd6NwgfgSxokzl9qkuY4pLR+Lvg1H3EjTRzZB1IjTV3cCNWmjmAgC1diCxpjC1ulDd8RhAtdihbljl/vmK1v2WuuWGgtVWdjDVwgS5ldCVZcydckjl5kzh3ynGwxHOooT6DSA8sQQwkpUeTyGrBlTp8eCNZkzh1rEeSmDx9ZB9JcyNYkzh2tlek5pjT8q3bqU2PPQokJgEPhTVxy2nBkDd6kDV2qEaOwGOvvWGxuViqoUSNlDd6nj6FvmKq13zKnkiJMgUZKwUWPg4poUaOkzp+kTh7nkCExG60033Jym7C03zKsVOdiC9tjjR10W7IeThrKAAOMAUYJgUULgUZdCZdqkqYu1ersk+fvFyww2q4z3fDqUmXhy5ulTp/yWi/Txo6JgEPLgYZLQUXKQUVJwkaSxExq1GTxWW3t1SpsU+gv1mvnUOLjTJ3izh3dTRkJwEQKgQWLQYaLAgcLQUYLQYZLAERezZaiTtmXyFJgjp0ijt1iTlqOAsjNQ0pKQASJwUVKQYYLgUZLAQZLAgcLgUZMAUYZyVRfCthPQoiIQAJOgcgYyBLKAIRLAUZNwsoMQcfKAQV"
212
+ width="16" height="16">
213
+ </div>
214
+ </div>
215
+ </div>`);
216
+ resolveCode(code);
217
+ if (server) {
218
+ displayInfo('Cleaning auth redirect server...');
219
+ server.close();
220
+ }
221
+ });
222
+ });
223
+
224
+ const server = redirectApp.listen(portParam, () => {
225
+ const addressInfo = server.address() as AddressInfo;
226
+ const port = addressInfo.port;
227
+ displayInfo(`OAuth callback server listening at ${port}`);
228
+ resolve({ port, server, codePromise });
229
+ });
230
+
231
+ server.on('error', (err) => {
232
+ // If the server fails to start, reject the outer promise.
233
+ reject(err);
234
+ });
235
+ });
236
+ }