@legionai/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 +52 -0
- package/package.json +23 -0
- package/src/cli.js +232 -0
- package/src/index.js +308 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Legion MCP Server
|
|
2
|
+
|
|
3
|
+
Access deployment logs directly from AI agents like Claude Code, Cursor, or Windsurf.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @legion/mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Add to your MCP config (e.g., `~/.config/claude/mcp.json`):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"legion": {
|
|
19
|
+
"command": "legion-mcp",
|
|
20
|
+
"env": {
|
|
21
|
+
"LEGION_AUTH_SERVER_URL": "https://auth.legion-ai.org",
|
|
22
|
+
"LEGION_ACCESS_TOKEN": "<your_token>"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Available Tools
|
|
30
|
+
|
|
31
|
+
| Tool | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `legion_list_projects` | List your connected projects |
|
|
34
|
+
| `legion_connect_project` | Connect a new project |
|
|
35
|
+
| `legion_get_logs` | Fetch logs with filters (level, limit) |
|
|
36
|
+
| `legion_get_errors` | Fetch error-level logs |
|
|
37
|
+
| `legion_disconnect_project` | Remove a project connection |
|
|
38
|
+
|
|
39
|
+
## Usage Example
|
|
40
|
+
|
|
41
|
+
Once configured, ask your AI agent:
|
|
42
|
+
|
|
43
|
+
- "Check my logs for errors"
|
|
44
|
+
- "What's the latest error in my-api?"
|
|
45
|
+
- "Connect my project for debugging"
|
|
46
|
+
|
|
47
|
+
## Security
|
|
48
|
+
|
|
49
|
+
- All log access is authenticated via your Legion account
|
|
50
|
+
- You can only access logs for projects you've connected
|
|
51
|
+
- Tokens are encrypted at rest
|
|
52
|
+
- Access is rate-limited (30 requests/minute)
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@legionai/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Legion MCP Server - Railway logs access for AI agents",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"legion-mcp": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"legion",
|
|
15
|
+
"railway",
|
|
16
|
+
"ai",
|
|
17
|
+
"agents"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Legion CLI Login
|
|
5
|
+
*
|
|
6
|
+
* Opens browser for OAuth login, receives token via local callback.
|
|
7
|
+
*
|
|
8
|
+
* Usage: legion-mcp login
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.legion');
|
|
18
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
19
|
+
|
|
20
|
+
const LEGION_URL = process.env.LEGION_URL || 'https://legion-ai.org';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get a random available port
|
|
24
|
+
*/
|
|
25
|
+
function getRandomPort() {
|
|
26
|
+
return Math.floor(Math.random() * (65535 - 49152) + 49152);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Open URL in default browser
|
|
31
|
+
*/
|
|
32
|
+
function openBrowser(url) {
|
|
33
|
+
const platform = process.platform;
|
|
34
|
+
let command;
|
|
35
|
+
|
|
36
|
+
if (platform === 'darwin') {
|
|
37
|
+
command = `open "${url}"`;
|
|
38
|
+
} else if (platform === 'win32') {
|
|
39
|
+
command = `start "${url}"`;
|
|
40
|
+
} else {
|
|
41
|
+
command = `xdg-open "${url}"`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
execSync(command);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Failed to open browser. Please open this URL manually:');
|
|
48
|
+
console.error(url);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Save config to ~/.legion/config.json
|
|
54
|
+
*/
|
|
55
|
+
function saveConfig(config) {
|
|
56
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
57
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const existing = fs.existsSync(CONFIG_FILE)
|
|
61
|
+
? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'))
|
|
62
|
+
: {};
|
|
63
|
+
|
|
64
|
+
const merged = { ...existing, ...config };
|
|
65
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load config from ~/.legion/config.json
|
|
70
|
+
*/
|
|
71
|
+
function loadConfig() {
|
|
72
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start local callback server and wait for token
|
|
80
|
+
*/
|
|
81
|
+
function startCallbackServer(port, state) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const timeout = setTimeout(() => {
|
|
84
|
+
server.close();
|
|
85
|
+
reject(new Error('Login timed out after 5 minutes'));
|
|
86
|
+
}, 5 * 60 * 1000);
|
|
87
|
+
|
|
88
|
+
const server = http.createServer((req, res) => {
|
|
89
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
90
|
+
|
|
91
|
+
if (url.pathname === '/callback') {
|
|
92
|
+
const token = url.searchParams.get('token');
|
|
93
|
+
const returnedState = url.searchParams.get('state');
|
|
94
|
+
const error = url.searchParams.get('error');
|
|
95
|
+
|
|
96
|
+
if (error) {
|
|
97
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
98
|
+
res.end(`
|
|
99
|
+
<html>
|
|
100
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
101
|
+
<h1 style="color: #ef4444;">Login Failed</h1>
|
|
102
|
+
<p>${error}</p>
|
|
103
|
+
<p>You can close this window.</p>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
106
|
+
`);
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
server.close();
|
|
109
|
+
reject(new Error(error));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (returnedState !== state) {
|
|
114
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
115
|
+
res.end('<html><body>Invalid state token</body></html>');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (token) {
|
|
120
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
121
|
+
res.end(`
|
|
122
|
+
<html>
|
|
123
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center; background: #0a0a0a; color: white;">
|
|
124
|
+
<h1 style="color: #22c55e;">✓ Logged in to Legion</h1>
|
|
125
|
+
<p style="color: #a1a1aa;">You can close this window and return to your terminal.</p>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
128
|
+
`);
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
server.close();
|
|
131
|
+
resolve(token);
|
|
132
|
+
} else {
|
|
133
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
134
|
+
res.end('<html><body>No token received</body></html>');
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
res.writeHead(404);
|
|
138
|
+
res.end('Not found');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
server.listen(port, () => {
|
|
143
|
+
console.log(`Waiting for login callback on port ${port}...`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
server.on('error', (err) => {
|
|
147
|
+
clearTimeout(timeout);
|
|
148
|
+
reject(err);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Main login flow
|
|
155
|
+
*/
|
|
156
|
+
async function login() {
|
|
157
|
+
console.log('🔐 Logging in to Legion...\n');
|
|
158
|
+
|
|
159
|
+
const port = getRandomPort();
|
|
160
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
161
|
+
const callback = `http://localhost:${port}/callback`;
|
|
162
|
+
|
|
163
|
+
const loginUrl = `${LEGION_URL}/cli-login?callback=${encodeURIComponent(callback)}&state=${state}`;
|
|
164
|
+
|
|
165
|
+
console.log('Opening browser for login...');
|
|
166
|
+
openBrowser(loginUrl);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const token = await startCallbackServer(port, state);
|
|
170
|
+
|
|
171
|
+
// Save token to config
|
|
172
|
+
saveConfig({
|
|
173
|
+
access_token: token,
|
|
174
|
+
auth_server_url: LEGION_URL.replace('legion-ai.org', 'auth.legion-ai.org'),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log('\n✓ Logged in successfully!');
|
|
178
|
+
console.log(` Token saved to ${CONFIG_FILE}`);
|
|
179
|
+
console.log('\n You can now use Legion MCP tools.');
|
|
180
|
+
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error('\n✗ Login failed:', err.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Logout: remove saved token
|
|
189
|
+
*/
|
|
190
|
+
function logout() {
|
|
191
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
192
|
+
const config = loadConfig();
|
|
193
|
+
delete config.access_token;
|
|
194
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
195
|
+
console.log('✓ Logged out of Legion');
|
|
196
|
+
} else {
|
|
197
|
+
console.log('Not logged in');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Show current status
|
|
203
|
+
*/
|
|
204
|
+
function status() {
|
|
205
|
+
const config = loadConfig();
|
|
206
|
+
if (config.access_token) {
|
|
207
|
+
console.log('✓ Logged in to Legion');
|
|
208
|
+
console.log(` Token: ${config.access_token.substring(0, 10)}...`);
|
|
209
|
+
console.log(` Config: ${CONFIG_FILE}`);
|
|
210
|
+
} else {
|
|
211
|
+
console.log('✗ Not logged in');
|
|
212
|
+
console.log(' Run: legion-mcp login');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// CLI handler
|
|
217
|
+
const command = process.argv[2];
|
|
218
|
+
|
|
219
|
+
switch (command) {
|
|
220
|
+
case 'login':
|
|
221
|
+
login();
|
|
222
|
+
break;
|
|
223
|
+
case 'logout':
|
|
224
|
+
logout();
|
|
225
|
+
break;
|
|
226
|
+
case 'status':
|
|
227
|
+
status();
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
// If no command, run the MCP server (imported from index.js)
|
|
231
|
+
require('./index.js');
|
|
232
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Legion MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Provides AI agents with access to deployment logs
|
|
7
|
+
* for iterative debugging and self-healing applications.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* legion-mcp --auth-server-url https://auth.yourlegion.com --token <legion_token>
|
|
11
|
+
*
|
|
12
|
+
* Or set environment variables:
|
|
13
|
+
* LEGION_AUTH_SERVER_URL
|
|
14
|
+
* LEGION_ACCESS_TOKEN
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
18
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
19
|
+
const {
|
|
20
|
+
CallToolRequestSchema,
|
|
21
|
+
ListToolsRequestSchema,
|
|
22
|
+
} = require('@modelcontextprotocol/sdk/types.js');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
// Load config from ~/.legion/config.json
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.legion', 'config.json');
|
|
29
|
+
try {
|
|
30
|
+
if (fs.existsSync(configPath)) {
|
|
31
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error('[LEGION MCP] Failed to load config:', err.message);
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const config = loadConfig();
|
|
40
|
+
|
|
41
|
+
// Configuration (env vars take precedence over config file)
|
|
42
|
+
const AUTH_SERVER_URL = process.env.LEGION_AUTH_SERVER_URL || config.auth_server_url || 'https://auth.legion-ai.org';
|
|
43
|
+
const ACCESS_TOKEN = process.env.LEGION_ACCESS_TOKEN || config.access_token;
|
|
44
|
+
|
|
45
|
+
// --- API Client ---
|
|
46
|
+
|
|
47
|
+
async function legionRequest(path, options = {}) {
|
|
48
|
+
if (!ACCESS_TOKEN) {
|
|
49
|
+
throw new Error('LEGION_ACCESS_TOKEN not set. Please authenticate first.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const url = `${AUTH_SERVER_URL}${path}`;
|
|
53
|
+
const response = await fetch(url, {
|
|
54
|
+
...options,
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
'Authorization': `Bearer ${ACCESS_TOKEN}`,
|
|
58
|
+
...options.headers,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
64
|
+
throw new Error(`Legion API error: ${error.message || response.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return response.json();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Tool Implementations ---
|
|
71
|
+
|
|
72
|
+
async function listProjects() {
|
|
73
|
+
const data = await legionRequest('/v1/railway/projects');
|
|
74
|
+
return data.projects;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function connectProject(projectName, projectId, environmentId, projectToken, serviceId) {
|
|
78
|
+
const data = await legionRequest('/v1/railway/connect', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
project_name: projectName,
|
|
82
|
+
project_id: projectId,
|
|
83
|
+
environment_id: environmentId,
|
|
84
|
+
project_token: projectToken,
|
|
85
|
+
service_id: serviceId,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
return data.connection;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function getLogs(connectionId, level, limit, filter) {
|
|
92
|
+
const params = new URLSearchParams();
|
|
93
|
+
if (level) params.set('level', level);
|
|
94
|
+
if (limit) params.set('limit', limit.toString());
|
|
95
|
+
if (filter) params.set('filter', filter);
|
|
96
|
+
|
|
97
|
+
const queryString = params.toString();
|
|
98
|
+
const path = `/v1/railway/logs/${connectionId}${queryString ? '?' + queryString : ''}`;
|
|
99
|
+
|
|
100
|
+
return legionRequest(path);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function disconnectProject(connectionId) {
|
|
104
|
+
return legionRequest(`/v1/railway/disconnect/${connectionId}`, {
|
|
105
|
+
method: 'DELETE',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- MCP Server ---
|
|
110
|
+
|
|
111
|
+
const server = new Server(
|
|
112
|
+
{
|
|
113
|
+
name: 'legion',
|
|
114
|
+
version: '1.0.0',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
capabilities: {
|
|
118
|
+
tools: {},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// List available tools
|
|
124
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
125
|
+
return {
|
|
126
|
+
tools: [
|
|
127
|
+
{
|
|
128
|
+
name: 'legion_list_projects',
|
|
129
|
+
description: 'List all projects connected to your Legion account for log access',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {},
|
|
133
|
+
required: [],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'legion_connect_project',
|
|
138
|
+
description: 'Connect a project to your Legion account for deployment log access',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
project_name: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'A friendly name for this project (e.g., "my-api")',
|
|
145
|
+
},
|
|
146
|
+
project_id: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: 'Project ID from your hosting platform',
|
|
149
|
+
},
|
|
150
|
+
environment_id: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Environment ID (e.g., production, staging)',
|
|
153
|
+
},
|
|
154
|
+
project_token: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
description: 'Project access token from your hosting platform settings',
|
|
157
|
+
},
|
|
158
|
+
service_id: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
description: 'Optional: Service ID for multi-service projects',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
required: ['project_name', 'project_id', 'environment_id', 'project_token'],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'legion_get_logs',
|
|
168
|
+
description: 'Fetch deployment logs from a connected project. Use this to debug errors and monitor your application.',
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
connection_id: {
|
|
173
|
+
type: 'string',
|
|
174
|
+
description: 'The connection ID returned when you connected the project',
|
|
175
|
+
},
|
|
176
|
+
level: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
enum: ['error', 'warn', 'info'],
|
|
179
|
+
description: 'Filter logs by level. Use "error" to see only errors.',
|
|
180
|
+
},
|
|
181
|
+
limit: {
|
|
182
|
+
type: 'number',
|
|
183
|
+
description: 'Maximum number of log lines to return (default: 100, max: 500)',
|
|
184
|
+
},
|
|
185
|
+
filter: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Advanced filter syntax (e.g., "@level:error")',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['connection_id'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'legion_get_errors',
|
|
195
|
+
description: 'Shortcut to fetch only error-level logs. Use this to quickly identify issues in your deployment.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
connection_id: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'The connection ID returned when you connected the project',
|
|
202
|
+
},
|
|
203
|
+
limit: {
|
|
204
|
+
type: 'number',
|
|
205
|
+
description: 'Maximum number of error lines to return (default: 50)',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ['connection_id'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'legion_disconnect_project',
|
|
213
|
+
description: 'Disconnect a project from your Legion account',
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
connection_id: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
description: 'The connection ID to disconnect',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
required: ['connection_id'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Handle tool calls
|
|
230
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
231
|
+
const { name, arguments: args } = request.params;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
let result;
|
|
235
|
+
|
|
236
|
+
switch (name) {
|
|
237
|
+
case 'legion_list_projects':
|
|
238
|
+
result = await listProjects();
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'legion_connect_project':
|
|
242
|
+
result = await connectProject(
|
|
243
|
+
args.project_name,
|
|
244
|
+
args.project_id,
|
|
245
|
+
args.environment_id,
|
|
246
|
+
args.project_token,
|
|
247
|
+
args.service_id
|
|
248
|
+
);
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'legion_get_logs':
|
|
252
|
+
result = await getLogs(
|
|
253
|
+
args.connection_id,
|
|
254
|
+
args.level,
|
|
255
|
+
args.limit,
|
|
256
|
+
args.filter
|
|
257
|
+
);
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
case 'legion_get_errors':
|
|
261
|
+
result = await getLogs(
|
|
262
|
+
args.connection_id,
|
|
263
|
+
'error',
|
|
264
|
+
args.limit || 50,
|
|
265
|
+
null
|
|
266
|
+
);
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'legion_disconnect_project':
|
|
270
|
+
result = await disconnectProject(args.connection_id);
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
default:
|
|
274
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: 'text',
|
|
281
|
+
text: JSON.stringify(result, null, 2),
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
} catch (error) {
|
|
286
|
+
return {
|
|
287
|
+
content: [
|
|
288
|
+
{
|
|
289
|
+
type: 'text',
|
|
290
|
+
text: `Error: ${error.message}`,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
isError: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Start server
|
|
299
|
+
async function main() {
|
|
300
|
+
const transport = new StdioServerTransport();
|
|
301
|
+
await server.connect(transport);
|
|
302
|
+
console.error('[LEGION MCP] Server started');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
main().catch((error) => {
|
|
306
|
+
console.error('[LEGION MCP] Fatal error:', error);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
});
|