langchain-mcp 1.2.5
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/README.md +57 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +239 -0
- package/dist/src/api-client.d.ts +74 -0
- package/dist/src/api-client.js +67 -0
- package/dist/src/config.d.ts +16 -0
- package/dist/src/config.js +31 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +13 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.js +153 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# langchain-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for searching LangChain, LangGraph & LangSmith documentation and source code.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g langchain-mcp
|
|
9
|
+
langchain-mcp login
|
|
10
|
+
claude mcp add langchain-mcp -- npx langchain-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"langchain-mcp": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["langchain-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## CLI Commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
langchain-mcp login # Login with Google
|
|
32
|
+
langchain-mcp status # Check login status and usage
|
|
33
|
+
langchain-mcp logout # Logout
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## MCP Tools
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `search_docs` | Search documentation, references, and tutorials about LangChain, LangGraph, LangSmith, and DeepAgents |
|
|
41
|
+
| `search_langchain_code` | Search LangChain source code |
|
|
42
|
+
| `search_langgraph_code` | Search LangGraph source code |
|
|
43
|
+
| `search_deepagent_code` | Search DeepAgent source code |
|
|
44
|
+
|
|
45
|
+
## Free Credits
|
|
46
|
+
|
|
47
|
+
New users get **$5 free credits** (~2000 searches).
|
|
48
|
+
|
|
49
|
+
## Links
|
|
50
|
+
|
|
51
|
+
- Website: https://langchain-mcp.xyz
|
|
52
|
+
- GitHub: https://github.com/baixianger/langchain-mcp
|
|
53
|
+
- Sponsor: https://ko-fi.com/baixianger
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { dirname, resolve } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { loadConfig, saveConfig, deleteConfig, getConfigPath, DEFAULT_API_URL, WEBSITE_URL } from '../src/config.js';
|
|
10
|
+
import { APIClient } from '../src/api-client.js';
|
|
11
|
+
import { createServer } from '../src/server.js';
|
|
12
|
+
// Get version from package.json
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf-8'));
|
|
15
|
+
// ASCII Art Banner - Generated with oh-my-logo (shade style)
|
|
16
|
+
function printBanner() {
|
|
17
|
+
const green = '\x1b[32m';
|
|
18
|
+
const reset = '\x1b[0m';
|
|
19
|
+
console.log(`
|
|
20
|
+
${green}░█░░░░░██░░█░░█░░███░████░█░░█░░██░░███░█░░█░░░░░░█░░█░████░███░
|
|
21
|
+
░█░░░░█ █░██░█░█ ░█ ░█░░█░█ █░ █ ░██░█░░░░░░████░█ ░█ █
|
|
22
|
+
░█░░░░████░█ ██░█░██░█░░░░████░████░░█░░█ ██░████░█ █░█░░░░███░
|
|
23
|
+
░█░░░░█ █░█░ █░█░ █░█░░░░█ █░█ █░░█░░█░ █░ ░█░░█░█░░░░█░░░
|
|
24
|
+
░████░█░░█░█░░█░███░░████░█░░█░█░░█░███░█░░█░░░░░░█░░█░████░█░░░${reset}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
function printDivider(char = '─', length = 70) {
|
|
28
|
+
console.log('\x1b[90m' + char.repeat(length) + '\x1b[0m');
|
|
29
|
+
}
|
|
30
|
+
function printSection(title) {
|
|
31
|
+
console.log(`\n\x1b[1m\x1b[33m${title}\x1b[0m`); // Bold yellow
|
|
32
|
+
}
|
|
33
|
+
const program = new Command();
|
|
34
|
+
program
|
|
35
|
+
.name('langchain-mcp')
|
|
36
|
+
.description('CLI for LangChain MCP server')
|
|
37
|
+
.version(pkg.version);
|
|
38
|
+
/**
|
|
39
|
+
* Default action - start MCP server (when no command given)
|
|
40
|
+
*/
|
|
41
|
+
program
|
|
42
|
+
.command('serve', { isDefault: true, hidden: true })
|
|
43
|
+
.description('Start MCP server')
|
|
44
|
+
.action(async () => {
|
|
45
|
+
const server = createServer();
|
|
46
|
+
const transport = new StdioServerTransport();
|
|
47
|
+
await server.connect(transport);
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Login command - Google OAuth
|
|
51
|
+
*/
|
|
52
|
+
program
|
|
53
|
+
.command('login')
|
|
54
|
+
.description('Login to LangChain MCP service')
|
|
55
|
+
.option('--no-browser', 'Print URL instead of opening browser')
|
|
56
|
+
.option('--api-url <url>', 'API server URL', DEFAULT_API_URL)
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
const existingConfig = loadConfig();
|
|
59
|
+
if (existingConfig) {
|
|
60
|
+
console.log('Already logged in.');
|
|
61
|
+
console.log('Run "langchain-mcp logout" first to log out.');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await loginWithWebFlow(options.apiUrl, options.browser);
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Web Flow login - opens browser for Google OAuth
|
|
68
|
+
*/
|
|
69
|
+
async function loginWithWebFlow(apiUrl, openBrowser) {
|
|
70
|
+
const port = 9876;
|
|
71
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
72
|
+
const server = http.createServer(async (req, res) => {
|
|
73
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
74
|
+
if (url.pathname === '/callback') {
|
|
75
|
+
const apiKey = url.searchParams.get('api_key');
|
|
76
|
+
const userJson = url.searchParams.get('user');
|
|
77
|
+
const error = url.searchParams.get('error');
|
|
78
|
+
if (error) {
|
|
79
|
+
const failureUrl = new URL(`${WEBSITE_URL}/failure.html`);
|
|
80
|
+
failureUrl.searchParams.set('error', error);
|
|
81
|
+
res.writeHead(302, { 'Location': failureUrl.toString() });
|
|
82
|
+
res.end();
|
|
83
|
+
console.error(`\nLogin failed: ${error}`);
|
|
84
|
+
server.close();
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
if (apiKey && userJson) {
|
|
88
|
+
try {
|
|
89
|
+
const user = JSON.parse(userJson);
|
|
90
|
+
// Save config
|
|
91
|
+
saveConfig({
|
|
92
|
+
api_key: apiKey,
|
|
93
|
+
api_url: apiUrl,
|
|
94
|
+
user,
|
|
95
|
+
});
|
|
96
|
+
printBanner();
|
|
97
|
+
printDivider();
|
|
98
|
+
console.log('\n \x1b[32m✓ Login Successful!\x1b[0m\n');
|
|
99
|
+
printSection('👤 User');
|
|
100
|
+
console.log(` Name: ${user.name || 'N/A'}`);
|
|
101
|
+
console.log(` Email: ${user.email}`);
|
|
102
|
+
printSection('💰 Credits');
|
|
103
|
+
const creditColor = user.credits > 5 ? '\x1b[32m' : user.credits > 1 ? '\x1b[33m' : '\x1b[31m';
|
|
104
|
+
console.log(` Remaining: ${creditColor}$${user.credits.toFixed(2)}\x1b[0m`);
|
|
105
|
+
printSection('☕ Support');
|
|
106
|
+
console.log(` Ko-fi: \x1b[36mhttps://ko-fi.com/baixianger\x1b[0m`);
|
|
107
|
+
printSection('⚙️ Config');
|
|
108
|
+
console.log(` Saved to: ${getConfigPath()}`);
|
|
109
|
+
console.log('');
|
|
110
|
+
printDivider();
|
|
111
|
+
console.log('');
|
|
112
|
+
// Redirect to homepage success page
|
|
113
|
+
const successUrl = new URL(`${WEBSITE_URL}/success.html`);
|
|
114
|
+
successUrl.searchParams.set('name', user.name || user.email);
|
|
115
|
+
res.writeHead(302, { 'Location': successUrl.toString() });
|
|
116
|
+
res.end();
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
server.close();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}, 1000);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
res.writeHead(400);
|
|
124
|
+
res.end('Login failed: invalid response');
|
|
125
|
+
server.close();
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
131
|
+
res.end(`
|
|
132
|
+
<!DOCTYPE html>
|
|
133
|
+
<html>
|
|
134
|
+
<head><title>Login Failed</title></head>
|
|
135
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
136
|
+
<h1>❌ Login Failed</h1>
|
|
137
|
+
<p>Missing API key</p>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|
|
140
|
+
`);
|
|
141
|
+
server.close();
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
res.writeHead(404);
|
|
147
|
+
res.end('Not found');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
server.listen(port, () => {
|
|
151
|
+
const authUrl = `${apiUrl}/auth/google?callback=${encodeURIComponent(callbackUrl)}`;
|
|
152
|
+
console.log('🔐 Logging in with Google...');
|
|
153
|
+
if (openBrowser) {
|
|
154
|
+
console.log('Opening browser for login...');
|
|
155
|
+
open(authUrl);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log('Open this URL in your browser:');
|
|
159
|
+
console.log(authUrl);
|
|
160
|
+
}
|
|
161
|
+
console.log(`\nWaiting for login callback on port ${port}...`);
|
|
162
|
+
});
|
|
163
|
+
// Timeout after 5 minutes
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
console.log('\n⏰ Login timeout. Please try again.');
|
|
166
|
+
server.close();
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}, 5 * 60 * 1000);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Status command - show current login status and usage
|
|
172
|
+
*/
|
|
173
|
+
program
|
|
174
|
+
.command('status')
|
|
175
|
+
.description('Show current login status and usage')
|
|
176
|
+
.action(async () => {
|
|
177
|
+
const config = loadConfig();
|
|
178
|
+
printBanner();
|
|
179
|
+
printDivider();
|
|
180
|
+
if (!config) {
|
|
181
|
+
console.log('\n \x1b[31m✗ Not logged in\x1b[0m');
|
|
182
|
+
console.log(' Run \x1b[36mlangchain-mcp login\x1b[0m to get started.\n');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// User Info Section
|
|
186
|
+
printSection('👤 User');
|
|
187
|
+
console.log(` Name: ${config.user?.name || 'N/A'}`);
|
|
188
|
+
console.log(` Email: ${config.user?.email || 'Unknown'}`);
|
|
189
|
+
console.log(` ID: ${config.user?.id || 'N/A'}`);
|
|
190
|
+
try {
|
|
191
|
+
const client = new APIClient(config.api_url, config.api_key);
|
|
192
|
+
const usage = await client.getUsage();
|
|
193
|
+
// Credits Section
|
|
194
|
+
printSection('💰 Credits');
|
|
195
|
+
const remaining = usage.credits.remaining;
|
|
196
|
+
const creditColor = remaining > 5 ? '\x1b[32m' : remaining > 1 ? '\x1b[33m' : '\x1b[31m';
|
|
197
|
+
console.log(` Remaining: ${creditColor}$${remaining.toFixed(2)}\x1b[0m`);
|
|
198
|
+
// Show Ko-fi prompt when low on credits
|
|
199
|
+
if (remaining <= 1) {
|
|
200
|
+
console.log(`\n \x1b[33m⚠️ Low credits! Support the project:\x1b[0m`);
|
|
201
|
+
console.log(` \x1b[36m☕ https://ko-fi.com/baixianger\x1b[0m`);
|
|
202
|
+
}
|
|
203
|
+
// Token Usage Section
|
|
204
|
+
printSection('📊 Token Usage');
|
|
205
|
+
console.log(` Today: ${usage.usage.today.tokens.toLocaleString().padStart(12)} tokens (${usage.usage.today.requests} requests)`);
|
|
206
|
+
console.log(` This Month: ${usage.usage.this_month.tokens.toLocaleString().padStart(12)} tokens (${usage.usage.this_month.requests} requests)`);
|
|
207
|
+
console.log(` All Time: ${usage.usage.all_time.tokens.toLocaleString().padStart(12)} tokens (${usage.usage.all_time.requests} requests)`);
|
|
208
|
+
// Support Section
|
|
209
|
+
printSection('☕ Support');
|
|
210
|
+
console.log(` Ko-fi: \x1b[36mhttps://ko-fi.com/baixianger\x1b[0m`);
|
|
211
|
+
// Config Section
|
|
212
|
+
printSection('⚙️ Config');
|
|
213
|
+
console.log(` Path: ${getConfigPath()}`);
|
|
214
|
+
console.log(` API: ${config.api_url}`);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
console.log(`\n \x1b[33m⚠️ Could not fetch usage: ${error.message}\x1b[0m`);
|
|
218
|
+
console.log(' Your API key may be invalid. Run \x1b[36mlangchain-mcp login\x1b[0m again.');
|
|
219
|
+
}
|
|
220
|
+
console.log('');
|
|
221
|
+
printDivider();
|
|
222
|
+
console.log('');
|
|
223
|
+
});
|
|
224
|
+
/**
|
|
225
|
+
* Logout command - remove local credentials
|
|
226
|
+
*/
|
|
227
|
+
program
|
|
228
|
+
.command('logout')
|
|
229
|
+
.description('Logout and remove local credentials')
|
|
230
|
+
.action(async () => {
|
|
231
|
+
const config = loadConfig();
|
|
232
|
+
if (!config) {
|
|
233
|
+
console.log('Not logged in.');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
deleteConfig();
|
|
237
|
+
console.log('✅ Logged out successfully.');
|
|
238
|
+
});
|
|
239
|
+
program.parse();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface SearchResult {
|
|
2
|
+
id: string;
|
|
3
|
+
content: string;
|
|
4
|
+
metadata: {
|
|
5
|
+
filePath: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
product?: string;
|
|
8
|
+
topic?: string;
|
|
9
|
+
codeType?: string;
|
|
10
|
+
};
|
|
11
|
+
distance: number;
|
|
12
|
+
repo: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchResponse {
|
|
15
|
+
results: SearchResult[];
|
|
16
|
+
usage: {
|
|
17
|
+
tokens_used: number;
|
|
18
|
+
credits_remaining: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface UsageResponse {
|
|
22
|
+
clerk_id: string;
|
|
23
|
+
credits: {
|
|
24
|
+
remaining_cents: number;
|
|
25
|
+
remaining: number;
|
|
26
|
+
};
|
|
27
|
+
usage: {
|
|
28
|
+
today: {
|
|
29
|
+
tokens: number;
|
|
30
|
+
requests: number;
|
|
31
|
+
};
|
|
32
|
+
this_month: {
|
|
33
|
+
tokens: number;
|
|
34
|
+
requests: number;
|
|
35
|
+
};
|
|
36
|
+
all_time: {
|
|
37
|
+
tokens: number;
|
|
38
|
+
requests: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface APIError {
|
|
43
|
+
error: {
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export declare class APIClient {
|
|
49
|
+
private baseUrl;
|
|
50
|
+
private sessionToken;
|
|
51
|
+
constructor(baseUrl: string, sessionToken: string);
|
|
52
|
+
private request;
|
|
53
|
+
searchDocs(input: {
|
|
54
|
+
query: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
language?: 'python' | 'javascript';
|
|
57
|
+
}): Promise<SearchResponse>;
|
|
58
|
+
searchLangchainCode(input: {
|
|
59
|
+
query: string;
|
|
60
|
+
limit?: number;
|
|
61
|
+
language?: 'python' | 'javascript';
|
|
62
|
+
}): Promise<SearchResponse>;
|
|
63
|
+
searchLanggraphCode(input: {
|
|
64
|
+
query: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
language?: 'python' | 'javascript';
|
|
67
|
+
}): Promise<SearchResponse>;
|
|
68
|
+
searchDeepagentCode(input: {
|
|
69
|
+
query: string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
language?: 'python' | 'javascript';
|
|
72
|
+
}): Promise<SearchResponse>;
|
|
73
|
+
getUsage(): Promise<UsageResponse>;
|
|
74
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Convert language from user-friendly names to API format
|
|
2
|
+
function mapLanguage(lang) {
|
|
3
|
+
if (!lang)
|
|
4
|
+
return undefined;
|
|
5
|
+
return lang === 'python' ? 'py' : 'js';
|
|
6
|
+
}
|
|
7
|
+
export class APIClient {
|
|
8
|
+
baseUrl;
|
|
9
|
+
sessionToken;
|
|
10
|
+
constructor(baseUrl, sessionToken) {
|
|
11
|
+
this.baseUrl = baseUrl;
|
|
12
|
+
this.sessionToken = sessionToken;
|
|
13
|
+
}
|
|
14
|
+
async request(method, endpoint, body) {
|
|
15
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
16
|
+
method,
|
|
17
|
+
headers: {
|
|
18
|
+
'Authorization': `Bearer ${this.sessionToken}`,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
},
|
|
21
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
22
|
+
});
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const error = data;
|
|
26
|
+
if (error.error?.code === 'INSUFFICIENT_CREDITS') {
|
|
27
|
+
throw new Error('Insufficient credits. Visit https://langchain-mcp.xyz to add more credits.');
|
|
28
|
+
}
|
|
29
|
+
if (error.error?.code === 'UNAUTHORIZED') {
|
|
30
|
+
throw new Error('Session expired. Run: langchain-mcp login');
|
|
31
|
+
}
|
|
32
|
+
throw new Error(error.error?.message || `API error: ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
async searchDocs(input) {
|
|
37
|
+
return this.request('POST', '/search/docs', {
|
|
38
|
+
query: input.query,
|
|
39
|
+
limit: input.limit,
|
|
40
|
+
language: mapLanguage(input.language),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async searchLangchainCode(input) {
|
|
44
|
+
return this.request('POST', '/search/langchain', {
|
|
45
|
+
query: input.query,
|
|
46
|
+
limit: input.limit,
|
|
47
|
+
language: mapLanguage(input.language),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async searchLanggraphCode(input) {
|
|
51
|
+
return this.request('POST', '/search/langgraph', {
|
|
52
|
+
query: input.query,
|
|
53
|
+
limit: input.limit,
|
|
54
|
+
language: mapLanguage(input.language),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async searchDeepagentCode(input) {
|
|
58
|
+
return this.request('POST', '/search/deepagent', {
|
|
59
|
+
query: input.query,
|
|
60
|
+
limit: input.limit,
|
|
61
|
+
language: mapLanguage(input.language),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async getUsage() {
|
|
65
|
+
return this.request('GET', '/usage');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface UserConfig {
|
|
2
|
+
api_key: string;
|
|
3
|
+
api_url: string;
|
|
4
|
+
user?: {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
name: string | null;
|
|
8
|
+
credits: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_API_URL: string;
|
|
12
|
+
export declare const WEBSITE_URL: string;
|
|
13
|
+
export declare function loadConfig(): UserConfig | null;
|
|
14
|
+
export declare function saveConfig(config: UserConfig): void;
|
|
15
|
+
export declare function deleteConfig(): void;
|
|
16
|
+
export declare function getConfigPath(): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.config', 'langchain-mcp');
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
export const DEFAULT_API_URL = process.env.LANGCHAIN_MCP_API_URL || 'https://api.langchain-mcp.xyz';
|
|
7
|
+
export const WEBSITE_URL = process.env.LANGCHAIN_MCP_WEBSITE_URL || 'https://langchain-mcp.xyz';
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
try {
|
|
10
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function saveConfig(config) {
|
|
21
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
23
|
+
}
|
|
24
|
+
export function deleteConfig() {
|
|
25
|
+
if (existsSync(CONFIG_FILE)) {
|
|
26
|
+
unlinkSync(CONFIG_FILE);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function getConfigPath() {
|
|
30
|
+
return CONFIG_FILE;
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createServer } from './server.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
console.error('LangChain MCP server running on stdio');
|
|
9
|
+
}
|
|
10
|
+
main().catch((error) => {
|
|
11
|
+
console.error('Failed to start server:', error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { APIClient } from './api-client.js';
|
|
5
|
+
// Schemas
|
|
6
|
+
const searchDocsSchema = z.object({
|
|
7
|
+
query: z.string().describe('Search query'),
|
|
8
|
+
limit: z.number().int().min(1).max(20).default(5).describe('Max results (1-20)'),
|
|
9
|
+
language: z.enum(['python', 'javascript']).optional().describe('Filter by language'),
|
|
10
|
+
});
|
|
11
|
+
const searchCodeSchema = z.object({
|
|
12
|
+
query: z.string().describe('Search query'),
|
|
13
|
+
limit: z.number().int().min(1).max(20).default(5).describe('Max results (1-20)'),
|
|
14
|
+
language: z.enum(['python', 'javascript']).optional().describe('Filter by language'),
|
|
15
|
+
});
|
|
16
|
+
function formatDocsResults(results) {
|
|
17
|
+
if (results.length === 0) {
|
|
18
|
+
return 'No results found.';
|
|
19
|
+
}
|
|
20
|
+
return results.map((r, i) => {
|
|
21
|
+
const meta = r.metadata;
|
|
22
|
+
const header = `## ${i + 1}. ${meta.filePath}`;
|
|
23
|
+
const info = [
|
|
24
|
+
meta.product && `Product: ${meta.product}`,
|
|
25
|
+
meta.language && `Language: ${meta.language}`,
|
|
26
|
+
meta.topic && `Topic: ${meta.topic}`,
|
|
27
|
+
].filter(Boolean).join(' | ');
|
|
28
|
+
const content = r.content.length > 1500 ? r.content.slice(0, 1500) + '...' : r.content;
|
|
29
|
+
return `${header}\n${info}\n\n${content}`;
|
|
30
|
+
}).join('\n\n---\n\n');
|
|
31
|
+
}
|
|
32
|
+
function formatCodeResults(results) {
|
|
33
|
+
if (results.length === 0) {
|
|
34
|
+
return 'No results found.';
|
|
35
|
+
}
|
|
36
|
+
return results.map((r, i) => {
|
|
37
|
+
const meta = r.metadata;
|
|
38
|
+
const lang = meta.language === 'python' || meta.language === 'py' ? 'python' : 'typescript';
|
|
39
|
+
const header = `## ${i + 1}. ${meta.filePath}`;
|
|
40
|
+
const info = [
|
|
41
|
+
meta.codeType && `Type: ${meta.codeType}`,
|
|
42
|
+
meta.product && `Product: ${meta.product}`,
|
|
43
|
+
].filter(Boolean).join(' | ');
|
|
44
|
+
const content = r.content.length > 2000 ? r.content.slice(0, 2000) + '...' : r.content;
|
|
45
|
+
return `${header}\n${info}\n\n\`\`\`${lang}\n${content}\n\`\`\``;
|
|
46
|
+
}).join('\n\n---\n\n');
|
|
47
|
+
}
|
|
48
|
+
const LOGIN_MESSAGE = `## Not logged in
|
|
49
|
+
|
|
50
|
+
Please login first by running this command in your terminal:
|
|
51
|
+
|
|
52
|
+
\`\`\`bash
|
|
53
|
+
langchain-mcp login
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
This will open a browser for Google authentication.
|
|
57
|
+
After logging in, restart Claude to use the LangChain MCP tools.
|
|
58
|
+
|
|
59
|
+
For more info: https://langchain-mcp.xyz`;
|
|
60
|
+
export function createServer() {
|
|
61
|
+
const config = loadConfig();
|
|
62
|
+
const isLoggedIn = !!config;
|
|
63
|
+
const apiClient = isLoggedIn ? new APIClient(config.api_url, config.api_key) : null;
|
|
64
|
+
const server = new McpServer({
|
|
65
|
+
name: 'langchain-mcp',
|
|
66
|
+
version: '1.2.5',
|
|
67
|
+
});
|
|
68
|
+
// search_docs
|
|
69
|
+
server.tool('search_docs', 'Search documentation, references, and tutorials about LangChain, LangGraph, LangSmith, and DeepAgents.', searchDocsSchema.shape, async (input) => {
|
|
70
|
+
if (!apiClient) {
|
|
71
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const params = searchDocsSchema.parse(input);
|
|
75
|
+
const response = await apiClient.searchDocs(params);
|
|
76
|
+
const formatted = formatDocsResults(response.results);
|
|
77
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// search_langchain_code
|
|
90
|
+
server.tool('search_langchain_code', 'Search LangChain source code. Returns relevant code snippets.', searchCodeSchema.shape, async (input) => {
|
|
91
|
+
if (!apiClient) {
|
|
92
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const params = searchCodeSchema.parse(input);
|
|
96
|
+
const response = await apiClient.searchLangchainCode(params);
|
|
97
|
+
const formatted = formatCodeResults(response.results);
|
|
98
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// search_langgraph_code
|
|
111
|
+
server.tool('search_langgraph_code', 'Search LangGraph source code. Returns relevant code snippets.', searchCodeSchema.shape, async (input) => {
|
|
112
|
+
if (!apiClient) {
|
|
113
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const params = searchCodeSchema.parse(input);
|
|
117
|
+
const response = await apiClient.searchLanggraphCode(params);
|
|
118
|
+
const formatted = formatCodeResults(response.results);
|
|
119
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// search_deepagent_code
|
|
132
|
+
server.tool('search_deepagent_code', 'Search DeepAgent source code. Returns relevant code snippets.', searchCodeSchema.shape, async (input) => {
|
|
133
|
+
if (!apiClient) {
|
|
134
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const params = searchCodeSchema.parse(input);
|
|
138
|
+
const response = await apiClient.searchDeepagentCode(params);
|
|
139
|
+
const formatted = formatCodeResults(response.results);
|
|
140
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return server;
|
|
153
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "langchain-mcp",
|
|
3
|
+
"version": "1.2.5",
|
|
4
|
+
"description": "Give your AI assistant complete LangChain, LangGraph & LangSmith knowledge",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"langchain-mcp": "dist/bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "tsx watch src/index.ts",
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"cli": "tsx bin/cli.ts"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"langchain",
|
|
23
|
+
"langgraph",
|
|
24
|
+
"langsmith",
|
|
25
|
+
"claude",
|
|
26
|
+
"ai",
|
|
27
|
+
"documentation"
|
|
28
|
+
],
|
|
29
|
+
"author": "baixianger",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/baixianger/langchain-mcp"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://langchain-mcp.xyz",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
38
|
+
"commander": "^12.1.0",
|
|
39
|
+
"open": "^10.1.0",
|
|
40
|
+
"zod": "^3.24.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"tsx": "^4.19.0",
|
|
45
|
+
"typescript": "^5.7.0"
|
|
46
|
+
}
|
|
47
|
+
}
|