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.
- package/.gsloth.config_.mjs +18 -0
- package/README.md +8 -1
- package/assets/release-notes/v0_8_0.md +13 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/Invocation.d.ts +1 -1
- package/dist/core/Invocation.js +22 -7
- package/dist/core/Invocation.js.map +1 -1
- package/dist/filePathUtils.js +1 -2
- package/dist/filePathUtils.js.map +1 -1
- package/dist/globalConfigUtils.d.ts +28 -0
- package/dist/globalConfigUtils.js +61 -0
- package/dist/globalConfigUtils.js.map +1 -0
- package/dist/mcp/OAuthClientProviderImpl.d.ts +36 -0
- package/dist/mcp/OAuthClientProviderImpl.js +197 -0
- package/dist/mcp/OAuthClientProviderImpl.js.map +1 -0
- package/docs/CONFIGURATION.md +37 -2
- package/eslint.config.js +7 -1
- package/package.json +3 -1
- package/src/constants.ts +3 -0
- package/src/core/Invocation.ts +23 -9
- package/src/filePathUtils.ts +1 -3
- package/src/globalConfigUtils.ts +71 -0
- package/src/mcp/OAuthClientProviderImpl.ts +236 -0
package/.gsloth.config_.mjs
CHANGED
|
@@ -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
|
-
[](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml) [](https://github.com/andruhon/gaunt-sloth-assistant/actions/workflows/ci.yml) [](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.
|
package/dist/constants.d.ts
CHANGED
|
@@ -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';
|
package/dist/constants.js.map
CHANGED
|
@@ -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
|
}
|
package/dist/core/Invocation.js
CHANGED
|
@@ -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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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":"AACA,OAAO,EAAa,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"Invocation.js","sourceRoot":"","sources":["../../src/core/Invocation.ts"],"names":[],"mappings":"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"}
|
package/dist/filePathUtils.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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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"}
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -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
|
-
|
|
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 = [
|
|
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.
|
|
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';
|
package/src/core/Invocation.ts
CHANGED
|
@@ -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
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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;
|
package/src/filePathUtils.ts
CHANGED
|
@@ -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
|
+
}
|