@townco/cli 0.1.85 → 0.1.88
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/dist/commands/deploy.js +2 -25
- package/package.json +9 -9
- package/dist/commands/delete.d.ts +0 -1
- package/dist/commands/delete.js +0 -60
- package/dist/commands/edit.d.ts +0 -1
- package/dist/commands/edit.js +0 -92
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +0 -55
- package/dist/commands/mcp-add.d.ts +0 -14
- package/dist/commands/mcp-add.js +0 -494
- package/dist/commands/mcp-list.d.ts +0 -3
- package/dist/commands/mcp-list.js +0 -63
- package/dist/commands/mcp-remove.d.ts +0 -3
- package/dist/commands/mcp-remove.js +0 -120
- package/dist/commands/tool-add.d.ts +0 -6
- package/dist/commands/tool-add.js +0 -349
- package/dist/commands/tool-list.d.ts +0 -3
- package/dist/commands/tool-list.js +0 -61
- package/dist/commands/tool-register.d.ts +0 -7
- package/dist/commands/tool-register.js +0 -291
- package/dist/commands/tool-remove.d.ts +0 -3
- package/dist/commands/tool-remove.js +0 -202
- package/dist/components/MergedLogsPane.d.ts +0 -11
- package/dist/components/MergedLogsPane.js +0 -205
- package/dist/lib/auth-storage.d.ts +0 -38
- package/dist/lib/auth-storage.js +0 -89
- package/dist/lib/mcp-storage.d.ts +0 -32
- package/dist/lib/mcp-storage.js +0 -111
package/dist/commands/mcp-add.js
DELETED
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { getAgentPath, listAgents } from "@townco/agent/storage";
|
|
5
|
-
import { MultiSelect, SingleSelect } from "@townco/ui/tui";
|
|
6
|
-
import { Box, render, Text, useApp, useInput } from "ink";
|
|
7
|
-
import TextInput from "ink-text-input";
|
|
8
|
-
import { useEffect, useState } from "react";
|
|
9
|
-
import { mcpConfigExists, saveMCPConfig } from "../lib/mcp-storage";
|
|
10
|
-
function TextInputStage({ title, value, onChange, onSubmit, onCancel, placeholder, }) {
|
|
11
|
-
useInput((_input, key) => {
|
|
12
|
-
if (key.escape) {
|
|
13
|
-
onCancel();
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: title }) }), _jsxs(Box, { children: [_jsxs(Text, { children: [">", " "] }), _jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit, ...(placeholder && { placeholder }) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Continue \u2022 Esc: Back" }) })] }));
|
|
17
|
-
}
|
|
18
|
-
function TransportSelectStage({ selected, onChange, onNext, onCancel, }) {
|
|
19
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select transport type:" }) }), _jsx(SingleSelect, { options: [
|
|
20
|
-
{
|
|
21
|
-
label: "stdio",
|
|
22
|
-
value: "stdio",
|
|
23
|
-
description: "Run MCP server as a local process",
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
label: "HTTP",
|
|
27
|
-
value: "http",
|
|
28
|
-
description: "Connect to MCP server over HTTP",
|
|
29
|
-
},
|
|
30
|
-
], selected: selected, onChange: (transport) => onChange(transport), onSubmit: (transport) => onNext(transport), onCancel: onCancel })] }));
|
|
31
|
-
}
|
|
32
|
-
function NameInputStage({ value, onChange, onNext, onBack, onError, }) {
|
|
33
|
-
const handleSubmit = () => {
|
|
34
|
-
const trimmed = value.trim();
|
|
35
|
-
if (!trimmed) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// Check for duplicate
|
|
39
|
-
if (mcpConfigExists(trimmed)) {
|
|
40
|
-
onError(`MCP server "${trimmed}" already exists`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
onNext(trimmed);
|
|
44
|
-
};
|
|
45
|
-
return (_jsx(TextInputStage, { title: "Enter MCP server name:", value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "my-mcp-server" }));
|
|
46
|
-
}
|
|
47
|
-
function StdioCommandStage({ serverName, value, onChange, onNext, onBack, }) {
|
|
48
|
-
const handleSubmit = () => {
|
|
49
|
-
const trimmed = value.trim();
|
|
50
|
-
if (!trimmed) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
onNext(trimmed);
|
|
54
|
-
};
|
|
55
|
-
return (_jsx(TextInputStage, { title: `Enter command to run MCP server: ${serverName}`, value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "npx @modelcontextprotocol/server-filesystem" }));
|
|
56
|
-
}
|
|
57
|
-
function StdioArgsStage({ serverName, args, onAddArg, onRemoveLastArg, onNext, onBack, }) {
|
|
58
|
-
const [argInput, setArgInput] = useState("");
|
|
59
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Add arguments for: ", serverName, " (", args.length, " added)"] }) }), args.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Current arguments:" }), args.map((arg, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { children: [index + 1, ". ", arg] }) }, arg)))] })), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " "] }), _jsx(TextInput, { value: argInput, onChange: setArgInput, onSubmit: () => {
|
|
60
|
-
const trimmed = argInput.trim();
|
|
61
|
-
if (trimmed) {
|
|
62
|
-
onAddArg(trimmed);
|
|
63
|
-
setArgInput("");
|
|
64
|
-
}
|
|
65
|
-
}, placeholder: "Enter argument (or leave empty and press Enter to continue)" })] }), _jsx(SingleSelect, { options: [
|
|
66
|
-
{
|
|
67
|
-
label: "Done adding arguments",
|
|
68
|
-
value: "done",
|
|
69
|
-
description: "Continue to review",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
label: "Remove last argument",
|
|
73
|
-
value: "remove",
|
|
74
|
-
description: args.length > 0
|
|
75
|
-
? `Remove: ${args[args.length - 1]}`
|
|
76
|
-
: "No arguments to remove",
|
|
77
|
-
},
|
|
78
|
-
], selected: null, onChange: () => { }, onSubmit: (value) => {
|
|
79
|
-
if (value === "done") {
|
|
80
|
-
onNext();
|
|
81
|
-
}
|
|
82
|
-
else if (value === "remove" && args.length > 0) {
|
|
83
|
-
onRemoveLastArg();
|
|
84
|
-
}
|
|
85
|
-
}, onCancel: onBack })] }));
|
|
86
|
-
}
|
|
87
|
-
function HttpUrlStage({ serverName, value, onChange, onNext, onBack, onError, }) {
|
|
88
|
-
const handleSubmit = () => {
|
|
89
|
-
const trimmed = value.trim();
|
|
90
|
-
if (!trimmed) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
// Basic URL validation
|
|
94
|
-
try {
|
|
95
|
-
new URL(trimmed);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
onError("Invalid URL format");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
onNext(trimmed);
|
|
102
|
-
};
|
|
103
|
-
return (_jsx(TextInputStage, { title: `Enter HTTP URL for MCP server: ${serverName}`, value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "http://localhost:3000/mcp" }));
|
|
104
|
-
}
|
|
105
|
-
function HttpHeadersStage({ serverName, headers, onAddHeader, onNext, onBack, }) {
|
|
106
|
-
const [headerKeyInput, setHeaderKeyInput] = useState("");
|
|
107
|
-
const [headerValueInput, setHeaderValueInput] = useState("");
|
|
108
|
-
const [inputStage, setInputStage] = useState("key");
|
|
109
|
-
const headerEntries = Object.entries(headers);
|
|
110
|
-
useInput((_input, key) => {
|
|
111
|
-
if (key.escape && inputStage === "key") {
|
|
112
|
-
onBack();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Add HTTP headers for: ", serverName, " (", headerEntries.length, " added)"] }) }), headerEntries.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Current headers:" }), headerEntries.map(([key, value], index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { children: [index + 1, ". ", key, ": ", value] }) }, key)))] })), inputStage === "key" && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " Header name: "] }), _jsx(TextInput, { value: headerKeyInput, onChange: setHeaderKeyInput, onSubmit: () => {
|
|
116
|
-
const trimmed = headerKeyInput.trim();
|
|
117
|
-
if (trimmed) {
|
|
118
|
-
setInputStage("value");
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
onNext();
|
|
122
|
-
}
|
|
123
|
-
}, placeholder: "Enter header name (or leave empty to continue)" })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Submit header name or continue \u2022 Esc: Back" }) })] })), inputStage === "value" && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " Value for ", headerKeyInput, ":", " "] }), _jsx(TextInput, { value: headerValueInput, onChange: setHeaderValueInput, onSubmit: () => {
|
|
124
|
-
const trimmedValue = headerValueInput.trim();
|
|
125
|
-
if (trimmedValue) {
|
|
126
|
-
onAddHeader(headerKeyInput, trimmedValue);
|
|
127
|
-
setHeaderKeyInput("");
|
|
128
|
-
setHeaderValueInput("");
|
|
129
|
-
setInputStage("key");
|
|
130
|
-
}
|
|
131
|
-
}, placeholder: "Header value" })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Add header" }) })] }))] }));
|
|
132
|
-
}
|
|
133
|
-
function ReviewStage({ config, onEdit, onSave, onBack }) {
|
|
134
|
-
const [reviewSelection, setReviewSelection] = useState(null);
|
|
135
|
-
const reviewOptions = [
|
|
136
|
-
{
|
|
137
|
-
label: "Save configuration",
|
|
138
|
-
value: "continue",
|
|
139
|
-
description: "Save and finish",
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
label: "Edit name",
|
|
143
|
-
value: "name",
|
|
144
|
-
description: "Change the MCP server name",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
label: "Edit transport",
|
|
148
|
-
value: "transport",
|
|
149
|
-
description: "Change the transport type",
|
|
150
|
-
},
|
|
151
|
-
];
|
|
152
|
-
if (config.transport === "stdio") {
|
|
153
|
-
reviewOptions.push({
|
|
154
|
-
label: "Edit command",
|
|
155
|
-
value: "stdioCommand",
|
|
156
|
-
description: "Change the command",
|
|
157
|
-
}, {
|
|
158
|
-
label: "Edit arguments",
|
|
159
|
-
value: "stdioArgs",
|
|
160
|
-
description: "Change the arguments",
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
reviewOptions.push({
|
|
165
|
-
label: "Edit URL",
|
|
166
|
-
value: "httpUrl",
|
|
167
|
-
description: "Change the HTTP URL",
|
|
168
|
-
}, {
|
|
169
|
-
label: "Edit headers",
|
|
170
|
-
value: "httpHeaders",
|
|
171
|
-
description: "Change the HTTP headers",
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Review MCP Server Configuration:" }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Name: " }), config.name] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Transport: " }), config.transport] }), config.transport === "stdio" && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Command: " }), config.command] }), config.args && config.args.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Arguments:" }), config.args.map((arg, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { dimColor: true, children: [index + 1, ". ", arg] }) }, arg)))] }))] })), config.transport === "http" && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "URL: " }), config.url] }), config.headers && Object.keys(config.headers).length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Headers:" }), Object.entries(config.headers).map(([key, value], index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { dimColor: true, children: [index + 1, ". ", key, ": ", value] }) }, key)))] }))] }))] }), _jsx(SingleSelect, { options: reviewOptions, selected: reviewSelection, onChange: setReviewSelection, onSubmit: (value) => {
|
|
175
|
-
setReviewSelection(value);
|
|
176
|
-
if (value === "continue") {
|
|
177
|
-
onSave();
|
|
178
|
-
}
|
|
179
|
-
else if (value === "name" ||
|
|
180
|
-
value === "transport" ||
|
|
181
|
-
value === "stdioCommand" ||
|
|
182
|
-
value === "stdioArgs" ||
|
|
183
|
-
value === "httpUrl" ||
|
|
184
|
-
value === "httpHeaders") {
|
|
185
|
-
onEdit(value);
|
|
186
|
-
}
|
|
187
|
-
}, onCancel: onBack })] }));
|
|
188
|
-
}
|
|
189
|
-
function NoAgentsMessage({ onNext }) {
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
const timer = setTimeout(() => {
|
|
192
|
-
onNext();
|
|
193
|
-
}, 1000);
|
|
194
|
-
return () => clearTimeout(timer);
|
|
195
|
-
}, [onNext]);
|
|
196
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No agents found. MCP server will be saved globally." }), _jsx(Text, { dimColor: true, children: "You can attach it to agents later." })] }));
|
|
197
|
-
}
|
|
198
|
-
function AgentSelectionStage({ selectedAgents, onSelectedAgentsChange, onNext, onBack, }) {
|
|
199
|
-
const [availableAgents, setAvailableAgents] = useState([]);
|
|
200
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
// Fetch available agents
|
|
203
|
-
const fetchAgents = async () => {
|
|
204
|
-
try {
|
|
205
|
-
const agents = await listAgents();
|
|
206
|
-
setAvailableAgents(agents);
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
console.error("Error fetching agents:", error);
|
|
210
|
-
setAvailableAgents([]);
|
|
211
|
-
}
|
|
212
|
-
finally {
|
|
213
|
-
setIsLoading(false);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
fetchAgents();
|
|
217
|
-
}, []);
|
|
218
|
-
// If still loading, show loading message
|
|
219
|
-
if (isLoading) {
|
|
220
|
-
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { children: "Loading agents..." }) }));
|
|
221
|
-
}
|
|
222
|
-
// If no agents available, show info message and auto-proceed
|
|
223
|
-
if (availableAgents.length === 0) {
|
|
224
|
-
return _jsx(NoAgentsMessage, { onNext: onNext });
|
|
225
|
-
}
|
|
226
|
-
// Show agent selection UI
|
|
227
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Attach MCP server to agents (optional):" }) }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Select which agents should have access to this MCP server." }), _jsx(Text, { dimColor: true, children: "You can skip this step and attach agents later." })] }), _jsx(MultiSelect, { options: availableAgents.map((agent) => ({
|
|
228
|
-
label: agent,
|
|
229
|
-
value: agent,
|
|
230
|
-
})), selected: selectedAgents, onChange: onSelectedAgentsChange, onSubmit: onNext, onCancel: onBack })] }));
|
|
231
|
-
}
|
|
232
|
-
function DoneStage({ config, status, error, attachedAgents }) {
|
|
233
|
-
const { exit } = useApp();
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
if (status === "done") {
|
|
236
|
-
setImmediate(() => {
|
|
237
|
-
exit();
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}, [status, exit]);
|
|
241
|
-
if (status === "saving") {
|
|
242
|
-
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { children: "\u23F3 Saving MCP server configuration..." }) }));
|
|
243
|
-
}
|
|
244
|
-
if (status === "error") {
|
|
245
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "\u274C Error saving MCP server" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: error }) })] }));
|
|
246
|
-
}
|
|
247
|
-
if (status === "done") {
|
|
248
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\u2705 MCP server saved successfully!" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Name: ", config.name] }) }), attachedAgents.length > 0 && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { dimColor: true, children: ["Attached to agents: ", attachedAgents.join(", ")] }) }))] }));
|
|
249
|
-
}
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
// ============================================================================
|
|
253
|
-
// Main Component
|
|
254
|
-
// ============================================================================
|
|
255
|
-
function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand, args: initialArgs, }) {
|
|
256
|
-
// Determine the starting stage based on what's provided
|
|
257
|
-
const determineInitialStage = () => {
|
|
258
|
-
// If command provided, transport is stdio
|
|
259
|
-
if (initialCommand) {
|
|
260
|
-
return initialName ? "stdioArgs" : "name";
|
|
261
|
-
}
|
|
262
|
-
// If URL provided, transport is http
|
|
263
|
-
if (initialUrl) {
|
|
264
|
-
return initialName ? "httpHeaders" : "name";
|
|
265
|
-
}
|
|
266
|
-
return "transport";
|
|
267
|
-
};
|
|
268
|
-
const { exit } = useApp();
|
|
269
|
-
const [stage, setStage] = useState(determineInitialStage());
|
|
270
|
-
const [config, setConfig] = useState({
|
|
271
|
-
...(initialName && { name: initialName }),
|
|
272
|
-
...(initialCommand && { transport: "stdio" }),
|
|
273
|
-
...(initialUrl && !initialCommand && { transport: "http" }),
|
|
274
|
-
...(initialCommand && { command: initialCommand }),
|
|
275
|
-
...(initialArgs && initialArgs.length > 0 && { args: [...initialArgs] }),
|
|
276
|
-
...(initialUrl && { url: initialUrl }),
|
|
277
|
-
});
|
|
278
|
-
const [nameInput, setNameInput] = useState(initialName || "");
|
|
279
|
-
const [commandInput, setCommandInput] = useState(initialCommand || "");
|
|
280
|
-
const [urlInput, setUrlInput] = useState(initialUrl || "");
|
|
281
|
-
const [isEditingFromReview, setIsEditingFromReview] = useState(false);
|
|
282
|
-
const [saveStatus, setSaveStatus] = useState("pending");
|
|
283
|
-
const [saveError, setSaveError] = useState(null);
|
|
284
|
-
const [selectedAgents, setSelectedAgents] = useState([]);
|
|
285
|
-
const handleSave = (agentsToAttach) => {
|
|
286
|
-
setSaveStatus("saving");
|
|
287
|
-
// Save the config
|
|
288
|
-
try {
|
|
289
|
-
if (!config.name || !config.transport) {
|
|
290
|
-
setSaveStatus("error");
|
|
291
|
-
setSaveError("Missing required fields");
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (config.transport === "stdio" &&
|
|
295
|
-
(!config.command || !config.command.trim())) {
|
|
296
|
-
setSaveStatus("error");
|
|
297
|
-
setSaveError("Command is required for stdio transport");
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (config.transport === "http" && (!config.url || !config.url.trim())) {
|
|
301
|
-
setSaveStatus("error");
|
|
302
|
-
setSaveError("URL is required for http transport");
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const mcpConfig = {
|
|
306
|
-
name: config.name,
|
|
307
|
-
transport: config.transport,
|
|
308
|
-
...(config.transport === "stdio" && {
|
|
309
|
-
command: config.command,
|
|
310
|
-
args: config.args,
|
|
311
|
-
}),
|
|
312
|
-
...(config.transport === "http" && {
|
|
313
|
-
url: config.url,
|
|
314
|
-
...(config.headers && { headers: config.headers }),
|
|
315
|
-
}),
|
|
316
|
-
};
|
|
317
|
-
// Save to global MCP storage
|
|
318
|
-
saveMCPConfig(mcpConfig);
|
|
319
|
-
// Update selected agents
|
|
320
|
-
for (const agentName of agentsToAttach) {
|
|
321
|
-
try {
|
|
322
|
-
const agentPath = getAgentPath(agentName);
|
|
323
|
-
const agentJsonPath = join(agentPath, "agent.json");
|
|
324
|
-
// Read existing agent.json
|
|
325
|
-
const agentJsonContent = readFileSync(agentJsonPath, "utf-8");
|
|
326
|
-
const agentDef = JSON.parse(agentJsonContent);
|
|
327
|
-
// Add MCP config to mcps array (create array if doesn't exist)
|
|
328
|
-
if (!agentDef.mcps) {
|
|
329
|
-
agentDef.mcps = [];
|
|
330
|
-
}
|
|
331
|
-
// Check if this MCP server is already in the agent's config
|
|
332
|
-
const existingIndex = agentDef.mcps.findIndex((mcp) => mcp.name === mcpConfig.name);
|
|
333
|
-
if (existingIndex >= 0) {
|
|
334
|
-
// Update existing config
|
|
335
|
-
agentDef.mcps[existingIndex] = mcpConfig;
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
// Add new config
|
|
339
|
-
agentDef.mcps.push(mcpConfig);
|
|
340
|
-
}
|
|
341
|
-
// Write back to agent.json
|
|
342
|
-
writeFileSync(agentJsonPath, JSON.stringify(agentDef, null, 2));
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
console.error(`Error updating agent ${agentName}:`, error);
|
|
346
|
-
// Continue with other agents even if one fails
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
setSaveStatus("done");
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
setSaveStatus("error");
|
|
353
|
-
setSaveError(error instanceof Error ? error.message : "Unknown error occurred");
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
// Transport selection stage
|
|
357
|
-
if (stage === "transport") {
|
|
358
|
-
return (_jsx(TransportSelectStage, { selected: config.transport || null, onChange: (transport) => setConfig({ ...config, transport }), onNext: (transport) => {
|
|
359
|
-
setConfig({ ...config, transport });
|
|
360
|
-
if (isEditingFromReview) {
|
|
361
|
-
setIsEditingFromReview(false);
|
|
362
|
-
setStage("review");
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
setStage("name");
|
|
366
|
-
}
|
|
367
|
-
}, onCancel: () => exit() }));
|
|
368
|
-
}
|
|
369
|
-
// Name input stage
|
|
370
|
-
if (stage === "name") {
|
|
371
|
-
return (_jsx(NameInputStage, { value: nameInput, onChange: setNameInput, onNext: (name) => {
|
|
372
|
-
setConfig({ ...config, name });
|
|
373
|
-
if (isEditingFromReview) {
|
|
374
|
-
setIsEditingFromReview(false);
|
|
375
|
-
setStage("review");
|
|
376
|
-
}
|
|
377
|
-
else if (config.transport === "stdio") {
|
|
378
|
-
setStage("stdioCommand");
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
setStage("httpUrl");
|
|
382
|
-
}
|
|
383
|
-
}, onBack: () => {
|
|
384
|
-
if (initialCommand || initialUrl) {
|
|
385
|
-
exit();
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
setStage("transport");
|
|
389
|
-
}
|
|
390
|
-
}, onError: setSaveError }));
|
|
391
|
-
}
|
|
392
|
-
// Stdio command input stage
|
|
393
|
-
if (stage === "stdioCommand") {
|
|
394
|
-
return (_jsx(StdioCommandStage, { serverName: config.name || "", value: commandInput, onChange: setCommandInput, onNext: (command) => {
|
|
395
|
-
setConfig({ ...config, command });
|
|
396
|
-
if (isEditingFromReview) {
|
|
397
|
-
setIsEditingFromReview(false);
|
|
398
|
-
setStage("review");
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
setStage("stdioArgs");
|
|
402
|
-
}
|
|
403
|
-
}, onBack: () => {
|
|
404
|
-
if (initialCommand) {
|
|
405
|
-
exit();
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
setStage("name");
|
|
409
|
-
}
|
|
410
|
-
} }));
|
|
411
|
-
}
|
|
412
|
-
// Stdio args input stage
|
|
413
|
-
if (stage === "stdioArgs") {
|
|
414
|
-
return (_jsx(StdioArgsStage, { serverName: config.name || "", args: config.args || [], onAddArg: (arg) => setConfig({ ...config, args: [...(config.args || []), arg] }), onRemoveLastArg: () => setConfig({ ...config, args: (config.args || []).slice(0, -1) }), onNext: () => {
|
|
415
|
-
if (isEditingFromReview) {
|
|
416
|
-
setIsEditingFromReview(false);
|
|
417
|
-
}
|
|
418
|
-
setStage("review");
|
|
419
|
-
}, onBack: () => setStage("stdioCommand") }));
|
|
420
|
-
}
|
|
421
|
-
// HTTP URL input stage
|
|
422
|
-
if (stage === "httpUrl") {
|
|
423
|
-
return (_jsx(HttpUrlStage, { serverName: config.name || "", value: urlInput, onChange: setUrlInput, onNext: (url) => {
|
|
424
|
-
setConfig({ ...config, url });
|
|
425
|
-
if (isEditingFromReview) {
|
|
426
|
-
setIsEditingFromReview(false);
|
|
427
|
-
setStage("review");
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
setStage("httpHeaders");
|
|
431
|
-
}
|
|
432
|
-
}, onBack: () => {
|
|
433
|
-
if (initialUrl) {
|
|
434
|
-
exit();
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
setStage("name");
|
|
438
|
-
}
|
|
439
|
-
}, onError: setSaveError }));
|
|
440
|
-
}
|
|
441
|
-
// HTTP Headers input stage
|
|
442
|
-
if (stage === "httpHeaders") {
|
|
443
|
-
return (_jsx(HttpHeadersStage, { serverName: config.name || "", headers: config.headers || {}, onAddHeader: (key, value) => setConfig({
|
|
444
|
-
...config,
|
|
445
|
-
headers: { ...(config.headers || {}), [key]: value },
|
|
446
|
-
}), onNext: () => {
|
|
447
|
-
if (isEditingFromReview) {
|
|
448
|
-
setIsEditingFromReview(false);
|
|
449
|
-
}
|
|
450
|
-
setStage("review");
|
|
451
|
-
}, onBack: () => setStage("httpUrl") }));
|
|
452
|
-
}
|
|
453
|
-
// Review stage
|
|
454
|
-
if (stage === "review") {
|
|
455
|
-
return (_jsx(ReviewStage, { config: config, onEdit: (editStage) => {
|
|
456
|
-
setIsEditingFromReview(true);
|
|
457
|
-
setStage(editStage);
|
|
458
|
-
}, onSave: () => {
|
|
459
|
-
setStage("agentSelection");
|
|
460
|
-
}, onBack: () => {
|
|
461
|
-
if (config.transport === "stdio") {
|
|
462
|
-
setStage("stdioArgs");
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
setStage("httpHeaders");
|
|
466
|
-
}
|
|
467
|
-
} }));
|
|
468
|
-
}
|
|
469
|
-
// Agent selection stage
|
|
470
|
-
if (stage === "agentSelection") {
|
|
471
|
-
return (_jsx(AgentSelectionStage, { selectedAgents: selectedAgents, onSelectedAgentsChange: setSelectedAgents, onNext: () => {
|
|
472
|
-
handleSave(selectedAgents);
|
|
473
|
-
setStage("done");
|
|
474
|
-
}, onBack: () => setStage("review") }));
|
|
475
|
-
}
|
|
476
|
-
// Done stage
|
|
477
|
-
if (stage === "done") {
|
|
478
|
-
return (_jsx(DoneStage, { config: config, status: saveStatus, error: saveError, attachedAgents: selectedAgents }));
|
|
479
|
-
}
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
// ============================================================================
|
|
483
|
-
// Export and Runner
|
|
484
|
-
// ============================================================================
|
|
485
|
-
export default MCPAddApp;
|
|
486
|
-
export async function runMCPAdd(props = {}) {
|
|
487
|
-
// Set stdin to raw mode to capture input
|
|
488
|
-
if (process.stdin.isTTY) {
|
|
489
|
-
process.stdin.setRawMode(true);
|
|
490
|
-
}
|
|
491
|
-
const { waitUntilExit } = render(_jsx(MCPAddApp, { ...props }));
|
|
492
|
-
// Wait for the app to exit before returning
|
|
493
|
-
await waitUntilExit();
|
|
494
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, render, Text, useApp, useInput } from "ink";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { listMCPConfigs } from "../lib/mcp-storage";
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// Main Component
|
|
7
|
-
// ============================================================================
|
|
8
|
-
function MCPListApp() {
|
|
9
|
-
const [result, setResult] = useState(null);
|
|
10
|
-
const [loading, setLoading] = useState(true);
|
|
11
|
-
const { exit } = useApp();
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
function loadConfigs() {
|
|
14
|
-
try {
|
|
15
|
-
const configs = listMCPConfigs();
|
|
16
|
-
setResult({ configs });
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
20
|
-
setResult({ configs: [], error: errorMsg });
|
|
21
|
-
}
|
|
22
|
-
finally {
|
|
23
|
-
setLoading(false);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
loadConfigs();
|
|
27
|
-
}, []);
|
|
28
|
-
// Exit on any key press when not loading
|
|
29
|
-
useInput((_input, key) => {
|
|
30
|
-
if (!loading) {
|
|
31
|
-
if (key.return || key.escape || _input === "q") {
|
|
32
|
-
exit();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
if (loading) {
|
|
37
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "Loading MCP servers..." }) }));
|
|
38
|
-
}
|
|
39
|
-
if (result?.error) {
|
|
40
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "\u274C Error loading MCP servers" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: result.error }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
|
|
41
|
-
}
|
|
42
|
-
if (!result || result.configs.length === 0) {
|
|
43
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "No MCP servers configured" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Add one with: town mcp add" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
|
|
44
|
-
}
|
|
45
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Configured MCP Servers (", result.configs.length, ")"] }) }), result.configs.map((config, index) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "cyan", children: [index + 1, ". ", config.name] }) }), _jsx(Box, { paddingLeft: 3, children: config.transport === "http" ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transport: ", _jsx(Text, { color: "green", children: "HTTP" })] }), _jsxs(Text, { children: ["URL: ", config.url] }), config.headers && Object.keys(config.headers).length > 0 && (_jsxs(Text, { children: ["Headers:", " ", Object.entries(config.headers)
|
|
46
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
47
|
-
.join(", ")] }))] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transport: ", _jsx(Text, { color: "blue", children: "stdio" })] }), _jsxs(Text, { children: ["Command: ", config.command] }), config.args && config.args.length > 0 && (_jsxs(Text, { children: ["Args: ", config.args.join(" ")] }))] })) })] }, config.name))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use `town mcp remove` to remove a server or `town mcp add` to add one" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
|
|
48
|
-
}
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Export and Runner
|
|
51
|
-
// ============================================================================
|
|
52
|
-
export default MCPListApp;
|
|
53
|
-
export async function runMCPList() {
|
|
54
|
-
const { waitUntilExit, clear } = render(_jsx(MCPListApp, {}));
|
|
55
|
-
try {
|
|
56
|
-
await waitUntilExit();
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
clear();
|
|
60
|
-
// Ensure cursor is visible
|
|
61
|
-
process.stdout.write("\x1B[?25h");
|
|
62
|
-
}
|
|
63
|
-
}
|