langchain-mcp 1.0.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/README.md +56 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +186 -0
- package/dist/src/api-client.d.ts +73 -0
- package/dist/src/api-client.js +42 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +30 -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 +155 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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 GitHub/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_langchain_docs` | Search documentation |
|
|
41
|
+
| `search_langchain_code` | Search source code |
|
|
42
|
+
| `search_langchain` | Hybrid search (docs + code) |
|
|
43
|
+
|
|
44
|
+
## Free Credits
|
|
45
|
+
|
|
46
|
+
New users get **$5 free credits** (~150 searches).
|
|
47
|
+
|
|
48
|
+
## Links
|
|
49
|
+
|
|
50
|
+
- Website: https://langchain-mcp.xyz
|
|
51
|
+
- GitHub: https://github.com/baixianger/langchain-mcp
|
|
52
|
+
- Sponsor: https://github.com/sponsors/baixianger
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { loadConfig, saveConfig, deleteConfig, getConfigPath, DEFAULT_API_URL } from '../src/config.js';
|
|
6
|
+
import { APIClient } from '../src/api-client.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('langchain-mcp')
|
|
10
|
+
.description('CLI for LangChain MCP server')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
/**
|
|
13
|
+
* Login command - supports Google and GitHub OAuth
|
|
14
|
+
*/
|
|
15
|
+
program
|
|
16
|
+
.command('login')
|
|
17
|
+
.description('Login to LangChain MCP service')
|
|
18
|
+
.option('--provider <provider>', 'OAuth provider: google or github', 'google')
|
|
19
|
+
.option('--no-browser', 'Print URL instead of opening browser')
|
|
20
|
+
.option('--api-url <url>', 'API server URL', DEFAULT_API_URL)
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
const existingConfig = loadConfig();
|
|
23
|
+
if (existingConfig) {
|
|
24
|
+
console.log('Already logged in.');
|
|
25
|
+
console.log('Run "langchain-mcp logout" first to log out.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const provider = options.provider.toLowerCase();
|
|
29
|
+
if (provider !== 'google' && provider !== 'github') {
|
|
30
|
+
console.error('Invalid provider. Use --provider google or --provider github');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
await loginWithWebFlow(options.apiUrl, provider, options.browser);
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Web Flow login - opens browser for OAuth
|
|
37
|
+
*/
|
|
38
|
+
async function loginWithWebFlow(apiUrl, provider, openBrowser) {
|
|
39
|
+
const port = 9876;
|
|
40
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
41
|
+
const server = http.createServer(async (req, res) => {
|
|
42
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
43
|
+
if (url.pathname === '/callback') {
|
|
44
|
+
const apiKey = url.searchParams.get('api_key');
|
|
45
|
+
const userJson = url.searchParams.get('user');
|
|
46
|
+
const error = url.searchParams.get('error');
|
|
47
|
+
if (error) {
|
|
48
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
49
|
+
res.end(`
|
|
50
|
+
<!DOCTYPE html>
|
|
51
|
+
<html>
|
|
52
|
+
<head><title>Login Failed</title></head>
|
|
53
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
54
|
+
<h1>❌ Login Failed</h1>
|
|
55
|
+
<p>${error}</p>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
`);
|
|
59
|
+
console.error(`\nLogin failed: ${error}`);
|
|
60
|
+
server.close();
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (apiKey && userJson) {
|
|
64
|
+
try {
|
|
65
|
+
const user = JSON.parse(userJson);
|
|
66
|
+
// Save config
|
|
67
|
+
saveConfig({
|
|
68
|
+
api_key: apiKey,
|
|
69
|
+
api_url: apiUrl,
|
|
70
|
+
user,
|
|
71
|
+
});
|
|
72
|
+
console.log(`\n✅ Logged in as ${user.email}`);
|
|
73
|
+
console.log(`💰 Credits: $${user.credits.toFixed(2)} remaining`);
|
|
74
|
+
console.log(`📁 Config saved to ${getConfigPath()}`);
|
|
75
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
76
|
+
res.end(`
|
|
77
|
+
<!DOCTYPE html>
|
|
78
|
+
<html>
|
|
79
|
+
<head><title>Login Successful</title></head>
|
|
80
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
81
|
+
<h1>✅ Login Successful!</h1>
|
|
82
|
+
<p>Welcome, ${user.name || user.email}!</p>
|
|
83
|
+
<p>You can close this window and return to your terminal.</p>
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
|
86
|
+
`);
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
server.close();
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}, 1000);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
res.writeHead(400);
|
|
94
|
+
res.end('Login failed: invalid response');
|
|
95
|
+
server.close();
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
101
|
+
res.end(`
|
|
102
|
+
<!DOCTYPE html>
|
|
103
|
+
<html>
|
|
104
|
+
<head><title>Login Failed</title></head>
|
|
105
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
106
|
+
<h1>❌ Login Failed</h1>
|
|
107
|
+
<p>Missing API key</p>
|
|
108
|
+
</body>
|
|
109
|
+
</html>
|
|
110
|
+
`);
|
|
111
|
+
server.close();
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
res.writeHead(404);
|
|
117
|
+
res.end('Not found');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
server.listen(port, () => {
|
|
121
|
+
// OAuth URL via our API
|
|
122
|
+
const authUrl = `${apiUrl}/auth/${provider}?callback=${encodeURIComponent(callbackUrl)}`;
|
|
123
|
+
const providerName = provider === 'google' ? 'Google' : 'GitHub';
|
|
124
|
+
console.log(`🔐 Logging in with ${providerName}...`);
|
|
125
|
+
if (openBrowser) {
|
|
126
|
+
console.log('Opening browser for login...');
|
|
127
|
+
open(authUrl);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log('Open this URL in your browser:');
|
|
131
|
+
console.log(authUrl);
|
|
132
|
+
}
|
|
133
|
+
console.log(`\nWaiting for login callback on port ${port}...`);
|
|
134
|
+
});
|
|
135
|
+
// Timeout after 5 minutes
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
console.log('\n⏰ Login timeout. Please try again.');
|
|
138
|
+
server.close();
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}, 5 * 60 * 1000);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Status command - show current login status and usage
|
|
144
|
+
*/
|
|
145
|
+
program
|
|
146
|
+
.command('status')
|
|
147
|
+
.description('Show current login status and usage')
|
|
148
|
+
.action(async () => {
|
|
149
|
+
const config = loadConfig();
|
|
150
|
+
if (!config) {
|
|
151
|
+
console.log('❌ Not logged in.');
|
|
152
|
+
console.log('Run "langchain-mcp login" to log in.');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
console.log(`👤 Logged in as: ${config.user?.email || 'Unknown'}`);
|
|
156
|
+
console.log(`📁 Config: ${getConfigPath()}`);
|
|
157
|
+
try {
|
|
158
|
+
const client = new APIClient(config.api_url, config.api_key);
|
|
159
|
+
const usage = await client.getUsage();
|
|
160
|
+
console.log(`\n💰 Credits: $${usage.credits.remaining.toFixed(2)} remaining`);
|
|
161
|
+
console.log(`\n📊 Usage:`);
|
|
162
|
+
console.log(` Today: ${usage.usage.today.tokens.toLocaleString()} tokens (${usage.usage.today.requests} requests)`);
|
|
163
|
+
console.log(` This month: ${usage.usage.this_month.tokens.toLocaleString()} tokens (${usage.usage.this_month.requests} requests)`);
|
|
164
|
+
console.log(` All time: ${usage.usage.all_time.tokens.toLocaleString()} tokens (${usage.usage.all_time.requests} requests)`);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.log(`\n⚠️ Could not fetch usage: ${error.message}`);
|
|
168
|
+
console.log('Your API key may be invalid. Run "langchain-mcp login" to log in again.');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Logout command - remove local credentials
|
|
173
|
+
*/
|
|
174
|
+
program
|
|
175
|
+
.command('logout')
|
|
176
|
+
.description('Logout and remove local credentials')
|
|
177
|
+
.action(async () => {
|
|
178
|
+
const config = loadConfig();
|
|
179
|
+
if (!config) {
|
|
180
|
+
console.log('Not logged in.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
deleteConfig();
|
|
184
|
+
console.log('✅ Logged out successfully.');
|
|
185
|
+
});
|
|
186
|
+
program.parse();
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
product?: string;
|
|
57
|
+
language?: string;
|
|
58
|
+
}): Promise<SearchResponse>;
|
|
59
|
+
searchCode(input: {
|
|
60
|
+
query: string;
|
|
61
|
+
limit?: number;
|
|
62
|
+
product?: string;
|
|
63
|
+
language?: string;
|
|
64
|
+
code_type?: string;
|
|
65
|
+
}): Promise<SearchResponse>;
|
|
66
|
+
searchHybrid(input: {
|
|
67
|
+
query: string;
|
|
68
|
+
limit?: number;
|
|
69
|
+
include_docs?: boolean;
|
|
70
|
+
include_code?: boolean;
|
|
71
|
+
}): Promise<SearchResponse>;
|
|
72
|
+
getUsage(): Promise<UsageResponse>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class APIClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
sessionToken;
|
|
4
|
+
constructor(baseUrl, sessionToken) {
|
|
5
|
+
this.baseUrl = baseUrl;
|
|
6
|
+
this.sessionToken = sessionToken;
|
|
7
|
+
}
|
|
8
|
+
async request(method, endpoint, body) {
|
|
9
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
'Authorization': `Bearer ${this.sessionToken}`,
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
16
|
+
});
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
const error = data;
|
|
20
|
+
if (error.error?.code === 'INSUFFICIENT_CREDITS') {
|
|
21
|
+
throw new Error('Insufficient credits. Visit https://langchain-mcp.xyz to add more credits.');
|
|
22
|
+
}
|
|
23
|
+
if (error.error?.code === 'UNAUTHORIZED') {
|
|
24
|
+
throw new Error('Session expired. Run: langchain-mcp login');
|
|
25
|
+
}
|
|
26
|
+
throw new Error(error.error?.message || `API error: ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
async searchDocs(input) {
|
|
31
|
+
return this.request('POST', '/search/docs', input);
|
|
32
|
+
}
|
|
33
|
+
async searchCode(input) {
|
|
34
|
+
return this.request('POST', '/search/code', input);
|
|
35
|
+
}
|
|
36
|
+
async searchHybrid(input) {
|
|
37
|
+
return this.request('POST', '/search/hybrid', input);
|
|
38
|
+
}
|
|
39
|
+
async getUsage() {
|
|
40
|
+
return this.request('GET', '/usage');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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 function loadConfig(): UserConfig | null;
|
|
13
|
+
export declare function saveConfig(config: UserConfig): void;
|
|
14
|
+
export declare function deleteConfig(): void;
|
|
15
|
+
export declare function getConfigPath(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
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 function loadConfig() {
|
|
8
|
+
try {
|
|
9
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function saveConfig(config) {
|
|
20
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
22
|
+
}
|
|
23
|
+
export function deleteConfig() {
|
|
24
|
+
if (existsSync(CONFIG_FILE)) {
|
|
25
|
+
unlinkSync(CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function getConfigPath() {
|
|
29
|
+
return CONFIG_FILE;
|
|
30
|
+
}
|
|
@@ -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,155 @@
|
|
|
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
|
+
product: z.enum(['langsmith', 'langchain', 'langgraph', 'deepagents', 'oss']).optional().describe('Filter by product'),
|
|
10
|
+
language: z.enum(['python', 'javascript']).optional().describe('Filter by language'),
|
|
11
|
+
});
|
|
12
|
+
const searchCodeSchema = z.object({
|
|
13
|
+
query: z.string().describe('Search query'),
|
|
14
|
+
limit: z.number().int().min(1).max(20).default(5).describe('Max results (1-20)'),
|
|
15
|
+
product: z.enum(['langchain', 'langgraph', 'deepagents']).optional().describe('Filter by product'),
|
|
16
|
+
language: z.enum(['python', 'javascript']).optional().describe('Filter by language'),
|
|
17
|
+
code_type: z.enum(['function', 'class', 'module']).optional().describe('Filter by code type'),
|
|
18
|
+
});
|
|
19
|
+
const searchHybridSchema = z.object({
|
|
20
|
+
query: z.string().describe('Search query'),
|
|
21
|
+
limit: z.number().int().min(1).max(20).default(10).describe('Max results (1-20)'),
|
|
22
|
+
include_docs: z.boolean().default(true).describe('Include documentation'),
|
|
23
|
+
include_code: z.boolean().default(true).describe('Include source code'),
|
|
24
|
+
});
|
|
25
|
+
function formatDocsResults(results) {
|
|
26
|
+
if (results.length === 0) {
|
|
27
|
+
return 'No results found.';
|
|
28
|
+
}
|
|
29
|
+
return results.map((r, i) => {
|
|
30
|
+
const meta = r.metadata;
|
|
31
|
+
const header = `## ${i + 1}. ${meta.filePath}`;
|
|
32
|
+
const info = [
|
|
33
|
+
meta.product && `Product: ${meta.product}`,
|
|
34
|
+
meta.language && `Language: ${meta.language}`,
|
|
35
|
+
meta.topic && `Topic: ${meta.topic}`,
|
|
36
|
+
].filter(Boolean).join(' | ');
|
|
37
|
+
const content = r.content.length > 1500 ? r.content.slice(0, 1500) + '...' : r.content;
|
|
38
|
+
return `${header}\n${info}\n\n${content}`;
|
|
39
|
+
}).join('\n\n---\n\n');
|
|
40
|
+
}
|
|
41
|
+
function formatCodeResults(results) {
|
|
42
|
+
if (results.length === 0) {
|
|
43
|
+
return 'No results found.';
|
|
44
|
+
}
|
|
45
|
+
return results.map((r, i) => {
|
|
46
|
+
const meta = r.metadata;
|
|
47
|
+
const lang = meta.language === 'python' ? 'python' : 'typescript';
|
|
48
|
+
const header = `## ${i + 1}. ${meta.filePath}`;
|
|
49
|
+
const info = [
|
|
50
|
+
meta.codeType && `Type: ${meta.codeType}`,
|
|
51
|
+
meta.product && `Product: ${meta.product}`,
|
|
52
|
+
].filter(Boolean).join(' | ');
|
|
53
|
+
const content = r.content.length > 2000 ? r.content.slice(0, 2000) + '...' : r.content;
|
|
54
|
+
return `${header}\n${info}\n\n\`\`\`${lang}\n${content}\n\`\`\``;
|
|
55
|
+
}).join('\n\n---\n\n');
|
|
56
|
+
}
|
|
57
|
+
const LOGIN_MESSAGE = `## Not logged in
|
|
58
|
+
|
|
59
|
+
Please login first by running this command in your terminal:
|
|
60
|
+
|
|
61
|
+
\`\`\`bash
|
|
62
|
+
langchain-mcp login
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
This will open a browser for GitHub/Google authentication.
|
|
66
|
+
After logging in, restart Claude to use the LangChain MCP tools.
|
|
67
|
+
|
|
68
|
+
For more info: https://langchain-mcp.xyz`;
|
|
69
|
+
export function createServer() {
|
|
70
|
+
const config = loadConfig();
|
|
71
|
+
const isLoggedIn = !!config;
|
|
72
|
+
const apiClient = isLoggedIn ? new APIClient(config.api_url, config.api_key) : null;
|
|
73
|
+
const server = new McpServer({
|
|
74
|
+
name: 'langchain-mcp',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
});
|
|
77
|
+
// search_langchain_docs
|
|
78
|
+
server.tool('search_langchain_docs', 'Search LangChain documentation. Returns relevant docs based on your query.', searchDocsSchema.shape, async (input) => {
|
|
79
|
+
if (!apiClient) {
|
|
80
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const params = searchDocsSchema.parse(input);
|
|
84
|
+
const response = await apiClient.searchDocs(params);
|
|
85
|
+
const formatted = formatDocsResults(response.results);
|
|
86
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// search_langchain_code
|
|
99
|
+
server.tool('search_langchain_code', 'Search LangChain source code. Returns relevant code snippets.', searchCodeSchema.shape, async (input) => {
|
|
100
|
+
if (!apiClient) {
|
|
101
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const params = searchCodeSchema.parse(input);
|
|
105
|
+
const response = await apiClient.searchCode(params);
|
|
106
|
+
const formatted = formatCodeResults(response.results);
|
|
107
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
115
|
+
isError: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// search_langchain
|
|
120
|
+
server.tool('search_langchain', 'Hybrid search across LangChain docs and code.', searchHybridSchema.shape, async (input) => {
|
|
121
|
+
if (!apiClient) {
|
|
122
|
+
return { content: [{ type: 'text', text: LOGIN_MESSAGE }] };
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const params = searchHybridSchema.parse(input);
|
|
126
|
+
const response = await apiClient.searchHybrid(params);
|
|
127
|
+
// Format mixed results
|
|
128
|
+
const formatted = response.results.map((r, i) => {
|
|
129
|
+
const meta = r.metadata;
|
|
130
|
+
const isCode = r.repo !== 'docs';
|
|
131
|
+
const header = `## ${i + 1}. [${isCode ? 'Code' : 'Docs'}] ${meta.filePath}`;
|
|
132
|
+
if (isCode) {
|
|
133
|
+
const lang = meta.language === 'python' ? 'python' : 'typescript';
|
|
134
|
+
const content = r.content.length > 1500 ? r.content.slice(0, 1500) + '...' : r.content;
|
|
135
|
+
return `${header}\n\n\`\`\`${lang}\n${content}\n\`\`\``;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const content = r.content.length > 1500 ? r.content.slice(0, 1500) + '...' : r.content;
|
|
139
|
+
return `${header}\n\n${content}`;
|
|
140
|
+
}
|
|
141
|
+
}).join('\n\n---\n\n');
|
|
142
|
+
const footer = `\n\n---\n_${response.results.length} results | ${response.usage.tokens_used} tokens | $${response.usage.credits_remaining.toFixed(2)} remaining_`;
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: 'text', text: formatted + footer }],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
150
|
+
isError: true,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return server;
|
|
155
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "langchain-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for LangChain documentation and code search",
|
|
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
|
+
}
|