@xyz-credit/agent-cli 1.1.2 → 1.2.1
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/bin/xyz-agent.js +132 -59
- package/package.json +2 -1
- package/src/commands/auth.js +1 -1
- package/src/commands/connect.js +192 -39
- package/src/commands/docker.js +305 -49
- package/src/commands/market.js +201 -39
- package/src/commands/message.js +236 -0
- package/src/commands/setup.js +295 -0
- package/src/commands/start.js +357 -124
- package/src/services/dashboard.js +293 -0
- package/src/services/heartbeat.js +262 -0
- package/src/services/messaging.js +97 -0
- package/src/services/risk.js +131 -0
package/bin/xyz-agent.js
CHANGED
|
@@ -1,105 +1,178 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* xyz-agent CLI —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* npx @xyz-credit/agent-cli status # Check agent status
|
|
3
|
+
* xyz-agent CLI — @xyz-credit/agent-cli
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* setup Automated setup wizard (LLM, MCP, config)
|
|
7
|
+
* auth Authenticate with xyz.credit platform
|
|
8
|
+
* connect Bridge local MCP tools
|
|
9
|
+
* register-service Publish services to marketplace
|
|
10
|
+
* market Manage marketplace listings
|
|
11
|
+
* start Launch agent with dashboard & heartbeat
|
|
12
|
+
* start --headless Launch in headless mode
|
|
13
|
+
* start --daemon Launch as background daemon (pm2)
|
|
14
|
+
* status Show agent status
|
|
15
|
+
* generate-docker Generate Dockerfile
|
|
17
16
|
*/
|
|
18
|
-
const {
|
|
17
|
+
const { Command } = require('commander');
|
|
19
18
|
const chalk = require('chalk');
|
|
19
|
+
const boxen = require('boxen');
|
|
20
|
+
const { config } = require('../src/config');
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
const pkg = require('../package.json');
|
|
20
24
|
|
|
21
25
|
program
|
|
22
26
|
.name('xyz-agent')
|
|
23
|
-
.description('CLI
|
|
24
|
-
.version(
|
|
27
|
+
.description('xyz.credit AI Agent CLI — join the decentralized agent economy')
|
|
28
|
+
.version(pkg.version);
|
|
25
29
|
|
|
26
|
-
// ──
|
|
30
|
+
// ── Setup Wizard ──
|
|
27
31
|
program
|
|
28
|
-
.command('
|
|
29
|
-
.description('
|
|
30
|
-
.action(async (
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
.command('setup')
|
|
33
|
+
.description('Automated setup wizard — configure LLM, MCP, and agent identity')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
try {
|
|
36
|
+
const { setupCommand } = require('../src/commands/setup');
|
|
37
|
+
await setupCommand();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(chalk.red(` Setup error: ${e.message}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
33
42
|
});
|
|
34
43
|
|
|
35
|
-
// ── Auth
|
|
44
|
+
// ── Auth ──
|
|
36
45
|
program
|
|
37
46
|
.command('auth')
|
|
38
|
-
.description('Authenticate
|
|
47
|
+
.description('Authenticate with the xyz.credit platform')
|
|
39
48
|
.action(async () => {
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
try {
|
|
50
|
+
const { authCommand } = require('../src/commands/auth');
|
|
51
|
+
await authCommand();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(chalk.red(` Auth error: ${e.message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
42
56
|
});
|
|
43
57
|
|
|
44
|
-
// ── Connect
|
|
58
|
+
// ── Connect ──
|
|
45
59
|
program
|
|
46
60
|
.command('connect')
|
|
47
|
-
.description('
|
|
48
|
-
.option('-u, --url <url>', '
|
|
61
|
+
.description('Bridge local MCP tools to the network')
|
|
62
|
+
.option('-u, --url <url>', 'MCP server URL (SSE endpoint)')
|
|
49
63
|
.action(async (opts) => {
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
try {
|
|
65
|
+
const { connectCommand } = require('../src/commands/connect');
|
|
66
|
+
await connectCommand(opts);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error(chalk.red(` Connect error: ${e.message}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
52
71
|
});
|
|
53
72
|
|
|
54
|
-
// ── Register Service
|
|
73
|
+
// ── Register Service ──
|
|
55
74
|
program
|
|
56
75
|
.command('register-service')
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
.alias('register')
|
|
77
|
+
.description('Register your MCP tools as marketplace services')
|
|
78
|
+
.action(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const { registerServiceCommand } = require('../src/commands/register_service');
|
|
81
|
+
await registerServiceCommand();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(chalk.red(` Register error: ${e.message}`));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
63
86
|
});
|
|
64
87
|
|
|
65
|
-
// ── Market
|
|
88
|
+
// ── Market ──
|
|
66
89
|
program
|
|
67
90
|
.command('market')
|
|
68
|
-
.description('
|
|
69
|
-
.option('-
|
|
70
|
-
.option('--
|
|
91
|
+
.description('Browse and manage marketplace listings')
|
|
92
|
+
.option('-t, --template <type>', 'Create from template (analytics, api, content, data)')
|
|
93
|
+
.option('--dry-run', 'Preview without publishing')
|
|
71
94
|
.action(async (opts) => {
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
try {
|
|
96
|
+
const { marketCommand } = require('../src/commands/market');
|
|
97
|
+
await marketCommand(opts);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error(chalk.red(` Market error: ${e.message}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
74
102
|
});
|
|
75
103
|
|
|
76
|
-
// ── Start
|
|
104
|
+
// ── Start ──
|
|
77
105
|
program
|
|
78
106
|
.command('start')
|
|
79
|
-
.description('Start
|
|
80
|
-
.option('
|
|
107
|
+
.description('Start agent with dashboard, heartbeat & marketplace sync')
|
|
108
|
+
.option('--headless', 'Run in headless mode (no terminal UI)')
|
|
109
|
+
.option('--daemon [subcommand]', 'Run as background daemon (pm2). Subcommands: status, logs, stop')
|
|
81
110
|
.action(async (opts) => {
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
try {
|
|
112
|
+
const { startCommand } = require('../src/commands/start');
|
|
113
|
+
await startCommand(opts);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error(chalk.red(` Start error: ${e.message}`));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
84
118
|
});
|
|
85
119
|
|
|
86
|
-
// ── Generate Docker
|
|
120
|
+
// ── Generate Docker ──
|
|
87
121
|
program
|
|
88
122
|
.command('generate-docker')
|
|
89
|
-
.description('Generate
|
|
90
|
-
.option('-
|
|
123
|
+
.description('Generate Dockerfile for cloud deployment')
|
|
124
|
+
.option('-p, --preset <provider>', 'Cloud preset: aws, gcp, azure, railway, fly')
|
|
91
125
|
.action(async (opts) => {
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
try {
|
|
127
|
+
const { dockerCommand } = require('../src/commands/docker');
|
|
128
|
+
await dockerCommand(opts);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error(chalk.red(` Docker error: ${e.message}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
94
133
|
});
|
|
95
134
|
|
|
96
|
-
// ──
|
|
135
|
+
// ── Message ──
|
|
136
|
+
program
|
|
137
|
+
.command('message [action] [args...]')
|
|
138
|
+
.alias('msg')
|
|
139
|
+
.description('Agent-to-agent direct messaging (send, inbox, outbox, read, agents)')
|
|
140
|
+
.option('-s, --subject <subject>', 'Message subject')
|
|
141
|
+
.option('-m, --message <message>', 'Message content (alternative to positional)')
|
|
142
|
+
.option('--unread', 'Show only unread messages')
|
|
143
|
+
.action(async (action, args, opts) => {
|
|
144
|
+
try {
|
|
145
|
+
const { messageCommand } = require('../src/commands/message');
|
|
146
|
+
await messageCommand(action, args, opts);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(chalk.red(` Message error: ${e.message}`));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ── Status ──
|
|
97
154
|
program
|
|
98
155
|
.command('status')
|
|
99
|
-
.description('
|
|
156
|
+
.description('Show agent status and connectivity')
|
|
100
157
|
.action(async () => {
|
|
101
|
-
|
|
102
|
-
|
|
158
|
+
try {
|
|
159
|
+
const { statusCommand } = require('../src/commands/status');
|
|
160
|
+
await statusCommand();
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.error(chalk.red(` Status error: ${e.message}`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
103
165
|
});
|
|
104
166
|
|
|
167
|
+
// ── Default ──
|
|
168
|
+
if (process.argv.length <= 2) {
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(boxen(
|
|
171
|
+
chalk.bold.cyan(' xyz.credit Agent CLI\n\n') +
|
|
172
|
+
chalk.white(` Get started: ${chalk.cyan('xyz-agent setup')}\n`) +
|
|
173
|
+
chalk.dim(` View commands: ${chalk.white('xyz-agent --help')}`),
|
|
174
|
+
{ padding: 1, margin: 1, borderColor: 'cyan', borderStyle: 'round' }
|
|
175
|
+
));
|
|
176
|
+
}
|
|
177
|
+
|
|
105
178
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyz-credit/agent-cli",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "CLI for onboarding AI agents to xyz.credit — Device Flow Auth, MCP Bridge, Service Marketplace, Daemonization & Cloud Export",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xyz-agent": "./bin/xyz-agent.js"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"register-service": "node bin/xyz-agent.js register-service"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"blessed": "^0.1.81",
|
|
22
23
|
"boxen": "^5.1.2",
|
|
23
24
|
"chalk": "^4.1.2",
|
|
24
25
|
"commander": "^12.1.0",
|
package/src/commands/auth.js
CHANGED
|
@@ -43,7 +43,7 @@ async function authCommand() {
|
|
|
43
43
|
type: 'input',
|
|
44
44
|
name: 'platformUrl',
|
|
45
45
|
message: 'Platform URL:',
|
|
46
|
-
default: config.get('platformUrl') || 'https://agent-
|
|
46
|
+
default: config.get('platformUrl') || 'https://agent-messaging.preview.emergentagent.com',
|
|
47
47
|
validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
|
|
48
48
|
}]);
|
|
49
49
|
|
package/src/commands/connect.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Connect Command — MCP Bridge & Introspection Engine
|
|
3
3
|
*
|
|
4
|
-
* Connects to
|
|
4
|
+
* Connects to an MCP server (local or remote), discovers available tools,
|
|
5
5
|
* and prepares them for marketplace registration.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Streamable HTTP (POST JSON-RPC with Accept: application/json, text/event-stream)
|
|
9
|
+
* - SSE transport (GET /sse → get messages endpoint → POST JSON-RPC)
|
|
6
10
|
*/
|
|
7
11
|
const inquirer = require('inquirer');
|
|
8
12
|
const chalk = require('chalk');
|
|
@@ -10,21 +14,160 @@ const ora = require('ora');
|
|
|
10
14
|
const fetch = require('node-fetch');
|
|
11
15
|
const { config, isAuthenticated, getCredentials } = require('../config');
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
function parseSSEResponse(text) {
|
|
18
|
+
for (const line of text.split('\n')) {
|
|
19
|
+
if (line.startsWith('data: ')) {
|
|
20
|
+
try { return JSON.parse(line.slice(6)); } catch { /* skip */ }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function tryStreamableHttp(baseUrl, spinner) {
|
|
14
27
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* For SSE-based MCP servers, we use the JSON-RPC protocol.
|
|
28
|
+
* Try streamable HTTP transport.
|
|
29
|
+
* Derives candidate URLs from the input.
|
|
19
30
|
*/
|
|
20
|
-
const
|
|
31
|
+
const candidates = [];
|
|
32
|
+
const stripped = baseUrl.replace(/\/+$/, '');
|
|
33
|
+
|
|
34
|
+
// If URL ends with /sse, convert to streamable HTTP equivalents
|
|
35
|
+
if (stripped.endsWith('/sse')) {
|
|
36
|
+
const sseBase = stripped.replace(/\/sse$/, '');
|
|
37
|
+
// xyz.credit pattern: /api/mcp/sse → /api/mcp-http/mcp
|
|
38
|
+
const parts = sseBase.split('/');
|
|
39
|
+
const lastPart = parts[parts.length - 1];
|
|
40
|
+
const parentPath = parts.slice(0, -1).join('/');
|
|
41
|
+
candidates.push(`${parentPath}/${lastPart}-http/${lastPart}`);
|
|
42
|
+
candidates.push(`${sseBase}/mcp`);
|
|
43
|
+
candidates.push(sseBase);
|
|
44
|
+
} else {
|
|
45
|
+
candidates.push(stripped);
|
|
46
|
+
candidates.push(`${stripped}/mcp`);
|
|
47
|
+
// Also try deriving the -http pattern (e.g. /api/mcp → /api/mcp-http/mcp)
|
|
48
|
+
const parts = stripped.split('/');
|
|
49
|
+
const lastPart = parts[parts.length - 1];
|
|
50
|
+
const parentPath = parts.slice(0, -1).join('/');
|
|
51
|
+
candidates.push(`${parentPath}/${lastPart}-http/${lastPart}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const url of candidates) {
|
|
55
|
+
try {
|
|
56
|
+
spinner.text = `Trying ${url}...`;
|
|
57
|
+
const initRes = await fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'Accept': 'application/json, text/event-stream',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
id: 1,
|
|
66
|
+
method: 'initialize',
|
|
67
|
+
params: {
|
|
68
|
+
protocolVersion: '2025-03-26',
|
|
69
|
+
capabilities: {},
|
|
70
|
+
clientInfo: { name: 'xyz-agent-cli', version: '1.1.2' }
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
timeout: 15000,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!initRes.ok) continue;
|
|
77
|
+
|
|
78
|
+
const initText = await initRes.text();
|
|
79
|
+
const initData = parseSSEResponse(initText) || JSON.parse(initText);
|
|
80
|
+
if (!initData?.result) continue;
|
|
81
|
+
|
|
82
|
+
const sessionId = initRes.headers.get('mcp-session-id') || '';
|
|
83
|
+
spinner.text = 'Listing tools...';
|
|
84
|
+
|
|
85
|
+
const headers = {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
'Accept': 'application/json, text/event-stream',
|
|
88
|
+
};
|
|
89
|
+
if (sessionId) headers['Mcp-Session-Id'] = sessionId;
|
|
90
|
+
|
|
91
|
+
const toolsRes = await fetch(url, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers,
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
jsonrpc: '2.0',
|
|
96
|
+
id: 2,
|
|
97
|
+
method: 'tools/list',
|
|
98
|
+
params: {}
|
|
99
|
+
}),
|
|
100
|
+
timeout: 15000,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const toolsText = await toolsRes.text();
|
|
104
|
+
const toolsData = parseSSEResponse(toolsText) || JSON.parse(toolsText);
|
|
105
|
+
const tools = toolsData?.result?.tools || [];
|
|
106
|
+
|
|
107
|
+
spinner.succeed(`Connected via Streamable HTTP! Found ${tools.length} tools`);
|
|
108
|
+
return tools;
|
|
109
|
+
} catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function trySSETransport(mcpUrl, spinner) {
|
|
117
|
+
/**
|
|
118
|
+
* Try SSE transport: connect to SSE endpoint, get messages URL, POST to it.
|
|
119
|
+
*/
|
|
120
|
+
const sseUrl = mcpUrl.endsWith('/sse') ? mcpUrl : `${mcpUrl}/sse`;
|
|
21
121
|
|
|
22
122
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
123
|
+
spinner.text = `Trying SSE at ${sseUrl}...`;
|
|
124
|
+
|
|
125
|
+
// Fetch the SSE stream briefly to get the endpoint event
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
128
|
+
|
|
129
|
+
const sseRes = await fetch(sseUrl, {
|
|
130
|
+
headers: { 'Accept': 'text/event-stream' },
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!sseRes.ok) return null;
|
|
135
|
+
|
|
136
|
+
// Read SSE stream to find endpoint event
|
|
137
|
+
let messagesUrl = null;
|
|
138
|
+
const reader = sseRes.body;
|
|
139
|
+
let buffer = '';
|
|
140
|
+
|
|
141
|
+
await new Promise((resolve) => {
|
|
142
|
+
const onData = (chunk) => {
|
|
143
|
+
buffer += chunk.toString();
|
|
144
|
+
const lines = buffer.split('\n');
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (line.startsWith('data: ') && buffer.includes('event: endpoint')) {
|
|
147
|
+
messagesUrl = line.slice(6).trim();
|
|
148
|
+
reader.removeListener('data', onData);
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
controller.abort();
|
|
151
|
+
resolve();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
reader.on('data', onData);
|
|
157
|
+
setTimeout(() => { reader.removeListener('data', onData); resolve(); }, 8000);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!messagesUrl) return null;
|
|
161
|
+
|
|
162
|
+
// Make messages URL absolute
|
|
163
|
+
const base = new URL(sseUrl);
|
|
164
|
+
if (messagesUrl.startsWith('/')) {
|
|
165
|
+
messagesUrl = `${base.protocol}//${base.host}${messagesUrl}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
spinner.text = 'Initializing via SSE messages endpoint...';
|
|
169
|
+
|
|
170
|
+
const initRes = await fetch(messagesUrl, {
|
|
28
171
|
method: 'POST',
|
|
29
172
|
headers: { 'Content-Type': 'application/json' },
|
|
30
173
|
body: JSON.stringify({
|
|
@@ -34,53 +177,62 @@ async function discoverMcpTools(mcpUrl) {
|
|
|
34
177
|
params: {
|
|
35
178
|
protocolVersion: '2025-03-26',
|
|
36
179
|
capabilities: {},
|
|
37
|
-
clientInfo: { name: 'xyz-agent-cli', version: '1.
|
|
180
|
+
clientInfo: { name: 'xyz-agent-cli', version: '1.1.2' }
|
|
38
181
|
}
|
|
39
182
|
}),
|
|
40
|
-
timeout:
|
|
183
|
+
timeout: 15000,
|
|
41
184
|
});
|
|
42
185
|
|
|
43
|
-
if (!initRes.ok)
|
|
44
|
-
throw new Error(`MCP server returned ${initRes.status}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const initData = await initRes.json();
|
|
48
|
-
spinner.text = 'Listing available tools...';
|
|
49
|
-
|
|
50
|
-
// Get session ID from response headers if present
|
|
51
|
-
const sessionId = initRes.headers.get('mcp-session-id') || '';
|
|
186
|
+
if (!initRes.ok) return null;
|
|
52
187
|
|
|
53
|
-
|
|
54
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
55
|
-
if (sessionId) headers['mcp-session-id'] = sessionId;
|
|
188
|
+
spinner.text = 'Listing tools via SSE...';
|
|
56
189
|
|
|
57
|
-
const toolsRes = await fetch(
|
|
190
|
+
const toolsRes = await fetch(messagesUrl, {
|
|
58
191
|
method: 'POST',
|
|
59
|
-
headers,
|
|
192
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
193
|
body: JSON.stringify({
|
|
61
194
|
jsonrpc: '2.0',
|
|
62
195
|
id: 2,
|
|
63
196
|
method: 'tools/list',
|
|
64
197
|
params: {}
|
|
65
198
|
}),
|
|
66
|
-
timeout:
|
|
199
|
+
timeout: 15000,
|
|
67
200
|
});
|
|
68
201
|
|
|
69
|
-
|
|
70
|
-
|
|
202
|
+
if (!toolsRes.ok) return null;
|
|
203
|
+
|
|
204
|
+
const toolsText = await toolsRes.text();
|
|
205
|
+
const toolsData = parseSSEResponse(toolsText) || JSON.parse(toolsText);
|
|
206
|
+
const tools = toolsData?.result?.tools || [];
|
|
71
207
|
|
|
72
|
-
spinner.succeed(`Connected! Found ${tools.length} tools`);
|
|
208
|
+
spinner.succeed(`Connected via SSE! Found ${tools.length} tools`);
|
|
73
209
|
return tools;
|
|
74
|
-
} catch
|
|
75
|
-
spinner.fail(`Failed to connect to MCP server: ${e.message}`);
|
|
210
|
+
} catch {
|
|
76
211
|
return null;
|
|
77
212
|
}
|
|
78
213
|
}
|
|
79
214
|
|
|
215
|
+
async function discoverMcpTools(mcpUrl) {
|
|
216
|
+
const spinner = ora(`Connecting to MCP server...`).start();
|
|
217
|
+
|
|
218
|
+
// Try streamable HTTP first (faster, no streaming needed)
|
|
219
|
+
let tools = await tryStreamableHttp(mcpUrl, spinner);
|
|
220
|
+
if (tools !== null) return tools;
|
|
221
|
+
|
|
222
|
+
// Fallback to SSE transport
|
|
223
|
+
tools = await trySSETransport(mcpUrl, spinner);
|
|
224
|
+
if (tools !== null) return tools;
|
|
225
|
+
|
|
226
|
+
spinner.fail(`Failed to connect to MCP server at: ${mcpUrl}`);
|
|
227
|
+
console.log(chalk.dim(' Tried both Streamable HTTP and SSE transports.'));
|
|
228
|
+
console.log(chalk.dim(' Ensure the MCP server is running and accessible.\n'));
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
80
232
|
async function connectCommand(opts) {
|
|
81
233
|
console.log('');
|
|
82
234
|
console.log(chalk.bold.cyan(' MCP Bridge — Tool Introspection'));
|
|
83
|
-
console.log(chalk.dim(' Connect to your
|
|
235
|
+
console.log(chalk.dim(' Connect to your MCP server and discover tools\n'));
|
|
84
236
|
|
|
85
237
|
if (!isAuthenticated()) {
|
|
86
238
|
console.log(chalk.red(' Not authenticated. Run `xyz-agent auth` first.\n'));
|
|
@@ -93,7 +245,7 @@ async function connectCommand(opts) {
|
|
|
93
245
|
const answer = await inquirer.prompt([{
|
|
94
246
|
type: 'input',
|
|
95
247
|
name: 'mcpUrl',
|
|
96
|
-
message: '
|
|
248
|
+
message: 'MCP server URL:',
|
|
97
249
|
default: 'http://localhost:3001/mcp',
|
|
98
250
|
validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
|
|
99
251
|
}]);
|
|
@@ -117,10 +269,11 @@ async function connectCommand(opts) {
|
|
|
117
269
|
const params = tool.inputSchema?.properties
|
|
118
270
|
? Object.keys(tool.inputSchema.properties).join(', ')
|
|
119
271
|
: 'none';
|
|
272
|
+
const desc = (tool.description || 'No description').split('\n')[0].slice(0, 60);
|
|
120
273
|
console.log(
|
|
121
274
|
chalk.white(` ${chalk.dim(`${i + 1}.`)} ${chalk.cyan(tool.name)}`) +
|
|
122
|
-
chalk.dim(` — ${
|
|
123
|
-
chalk.dim(` (params:
|
|
275
|
+
chalk.dim(` — ${desc}`) +
|
|
276
|
+
chalk.dim(` (${params.length > 40 ? params.slice(0, 40) + '...' : params})`)
|
|
124
277
|
);
|
|
125
278
|
});
|
|
126
279
|
|
|
@@ -130,7 +283,7 @@ async function connectCommand(opts) {
|
|
|
130
283
|
// Store discovered tools in config for register-service
|
|
131
284
|
config.set('discoveredTools', tools.map(t => ({
|
|
132
285
|
name: t.name,
|
|
133
|
-
description: t.description || '',
|
|
286
|
+
description: (t.description || '').split('\n')[0].slice(0, 200),
|
|
134
287
|
input_schema: t.inputSchema || {},
|
|
135
288
|
})));
|
|
136
289
|
}
|