omnikey-cli 1.0.39 → 1.0.40
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 +59 -0
- package/backend-dist/agent/agentPrompts.js +51 -6
- package/backend-dist/agent/agentServer.js +26 -4
- package/backend-dist/agent/imageTool.js +2 -1
- package/backend-dist/agent/mcpPromptCache.js +35 -0
- package/backend-dist/agent/mcpRuntime.js +245 -0
- package/backend-dist/agent/utils.js +2 -2
- package/backend-dist/index.js +6 -3
- package/backend-dist/mcpServerRoutes.js +222 -0
- package/backend-dist/models/mcpServer.js +102 -0
- package/dist/index.js +47 -7
- package/dist/mcpServer.js +334 -0
- package/package.json +2 -1
- package/src/index.ts +53 -7
- package/src/mcpServer.ts +390 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.mcpServerRouter = mcpServerRouter;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const zod_1 = __importDefault(require("zod"));
|
|
9
|
+
const authMiddleware_1 = require("./authMiddleware");
|
|
10
|
+
const mcpServer_1 = require("./models/mcpServer");
|
|
11
|
+
const mcpPromptCache_1 = require("./agent/mcpPromptCache");
|
|
12
|
+
const mcpRuntime_1 = require("./agent/mcpRuntime");
|
|
13
|
+
const transportEnum = zod_1.default.enum(['stdio', 'http', 'sse']);
|
|
14
|
+
const urlSchema = zod_1.default
|
|
15
|
+
.string()
|
|
16
|
+
.max(1000)
|
|
17
|
+
.url({ message: 'url must be a valid URL.' })
|
|
18
|
+
.refine((v) => /^https?:\/\//i.test(v), {
|
|
19
|
+
message: 'url must use http:// or https:// scheme.',
|
|
20
|
+
});
|
|
21
|
+
const baseSchema = zod_1.default.object({
|
|
22
|
+
name: zod_1.default.string().min(1).max(100),
|
|
23
|
+
description: zod_1.default.string().max(500).nullable().optional(),
|
|
24
|
+
transport: transportEnum.optional(),
|
|
25
|
+
command: zod_1.default.string().max(500).nullable().optional(),
|
|
26
|
+
args: zod_1.default.array(zod_1.default.string()).optional(),
|
|
27
|
+
env: zod_1.default.record(zod_1.default.string(), zod_1.default.string()).optional(),
|
|
28
|
+
url: urlSchema.nullable().optional(),
|
|
29
|
+
headers: zod_1.default.record(zod_1.default.string(), zod_1.default.string()).optional(),
|
|
30
|
+
isEnabled: zod_1.default.boolean().optional(),
|
|
31
|
+
});
|
|
32
|
+
function validateTransportFields(transport, command, url) {
|
|
33
|
+
if (transport === 'stdio') {
|
|
34
|
+
if (!command || !command.trim())
|
|
35
|
+
return 'command is required when transport is "stdio".';
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (!url || !url.trim())
|
|
39
|
+
return `url is required when transport is "${transport}".`;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// NOTE: `env` and `headers` may contain secrets (API tokens, credentials, etc.). They are
|
|
44
|
+
// stored at rest in the application database and should be treated as sensitive. List
|
|
45
|
+
// responses redact secret values by default to reduce accidental disclosure; single-record
|
|
46
|
+
// reads return full values so that edit UIs can round-trip the configuration.
|
|
47
|
+
const REDACTED = '***';
|
|
48
|
+
function redactDict(dict) {
|
|
49
|
+
if (!dict)
|
|
50
|
+
return {};
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const key of Object.keys(dict)) {
|
|
53
|
+
out[key] = REDACTED;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function formatServer(server, options = {}) {
|
|
58
|
+
const redact = options.redactSecrets === true;
|
|
59
|
+
return {
|
|
60
|
+
id: server.id,
|
|
61
|
+
name: server.name,
|
|
62
|
+
description: server.description,
|
|
63
|
+
transport: server.transport,
|
|
64
|
+
command: server.command,
|
|
65
|
+
args: server.args,
|
|
66
|
+
env: redact ? redactDict(server.env) : server.env,
|
|
67
|
+
url: server.url,
|
|
68
|
+
headers: redact ? redactDict(server.headers) : server.headers,
|
|
69
|
+
isEnabled: server.isEnabled,
|
|
70
|
+
lastConnectedAt: server.lastConnectedAt,
|
|
71
|
+
lastError: server.lastError,
|
|
72
|
+
createdAt: server.createdAt,
|
|
73
|
+
updatedAt: server.updatedAt,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function mcpServerRouter() {
|
|
77
|
+
const router = express_1.default.Router();
|
|
78
|
+
router.get('/', authMiddleware_1.authMiddleware, async (_req, res) => {
|
|
79
|
+
const { logger, subscription } = res.locals;
|
|
80
|
+
try {
|
|
81
|
+
const servers = await mcpServer_1.MCPServer.findAll({
|
|
82
|
+
where: { subscriptionId: subscription.id },
|
|
83
|
+
order: [['name', 'ASC']],
|
|
84
|
+
});
|
|
85
|
+
res.json({ servers: servers.map((s) => formatServer(s, { redactSecrets: true })) });
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
logger.error('Error retrieving MCP servers.', { error: err });
|
|
89
|
+
res.status(500).json({ error: 'Failed to retrieve MCP servers.' });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
router.post('/', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
93
|
+
const { logger, subscription } = res.locals;
|
|
94
|
+
try {
|
|
95
|
+
const parsed = baseSchema.parse(req.body);
|
|
96
|
+
const transport = parsed.transport ?? 'stdio';
|
|
97
|
+
const command = parsed.command ?? null;
|
|
98
|
+
const url = parsed.url ?? null;
|
|
99
|
+
const validationError = validateTransportFields(transport, command, url);
|
|
100
|
+
if (validationError) {
|
|
101
|
+
return res.status(400).json({ error: validationError });
|
|
102
|
+
}
|
|
103
|
+
const server = await mcpServer_1.MCPServer.create({
|
|
104
|
+
subscriptionId: subscription.id,
|
|
105
|
+
name: parsed.name.trim(),
|
|
106
|
+
description: parsed.description ?? null,
|
|
107
|
+
transport,
|
|
108
|
+
command,
|
|
109
|
+
args: parsed.args ?? [],
|
|
110
|
+
env: parsed.env ?? {},
|
|
111
|
+
url,
|
|
112
|
+
headers: parsed.headers ?? {},
|
|
113
|
+
isEnabled: parsed.isEnabled ?? true,
|
|
114
|
+
});
|
|
115
|
+
(0, mcpPromptCache_1.invalidatePromptMcps)(subscription.id);
|
|
116
|
+
void (0, mcpRuntime_1.invalidateMcpRuntimeForServer)(server.id);
|
|
117
|
+
res.status(201).json(formatServer(server));
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logger.error('Error creating MCP server.', { error: err });
|
|
121
|
+
if (err instanceof zod_1.default.ZodError) {
|
|
122
|
+
return res.status(400).json({ error: 'Invalid MCP server data.' });
|
|
123
|
+
}
|
|
124
|
+
if (err?.name === 'SequelizeUniqueConstraintError') {
|
|
125
|
+
return res.status(409).json({ error: 'An MCP server with that name already exists.' });
|
|
126
|
+
}
|
|
127
|
+
res.status(500).json({ error: 'Failed to create MCP server.' });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
router.get('/:id', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
131
|
+
const { logger, subscription } = res.locals;
|
|
132
|
+
const { id } = req.params;
|
|
133
|
+
try {
|
|
134
|
+
const server = await mcpServer_1.MCPServer.findOne({
|
|
135
|
+
where: { id, subscriptionId: subscription.id },
|
|
136
|
+
});
|
|
137
|
+
if (!server) {
|
|
138
|
+
return res.status(404).json({ error: 'MCP server not found.' });
|
|
139
|
+
}
|
|
140
|
+
res.json(formatServer(server));
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
logger.error('Error retrieving MCP server.', { error: err });
|
|
144
|
+
res.status(500).json({ error: 'Failed to retrieve MCP server.' });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
router.patch('/:id', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
148
|
+
const { logger, subscription } = res.locals;
|
|
149
|
+
const { id } = req.params;
|
|
150
|
+
try {
|
|
151
|
+
const parsed = baseSchema.partial().parse(req.body);
|
|
152
|
+
const server = await mcpServer_1.MCPServer.findOne({
|
|
153
|
+
where: { id, subscriptionId: subscription.id },
|
|
154
|
+
});
|
|
155
|
+
if (!server) {
|
|
156
|
+
return res.status(404).json({ error: 'MCP server not found.' });
|
|
157
|
+
}
|
|
158
|
+
const transport = parsed.transport ?? server.transport;
|
|
159
|
+
const transportChanged = parsed.transport !== undefined && parsed.transport !== server.transport;
|
|
160
|
+
// When transport changes, clear fields incompatible with the new transport so
|
|
161
|
+
// stale credentials/config never persist across a transport switch.
|
|
162
|
+
const command = transportChanged
|
|
163
|
+
? (transport === 'stdio' ? (parsed.command !== undefined ? parsed.command : server.command) : null)
|
|
164
|
+
: (parsed.command !== undefined ? parsed.command : server.command);
|
|
165
|
+
const url = transportChanged
|
|
166
|
+
? (transport !== 'stdio' ? (parsed.url !== undefined ? parsed.url : server.url) : null)
|
|
167
|
+
: (parsed.url !== undefined ? parsed.url : server.url);
|
|
168
|
+
const args = transportChanged && transport !== 'stdio' ? [] : (parsed.args ?? server.args);
|
|
169
|
+
const env = transportChanged && transport !== 'stdio' ? {} : (parsed.env ?? server.env);
|
|
170
|
+
const headers = transportChanged && transport === 'stdio' ? {} : (parsed.headers ?? server.headers);
|
|
171
|
+
const validationError = validateTransportFields(transport, command, url);
|
|
172
|
+
if (validationError) {
|
|
173
|
+
return res.status(400).json({ error: validationError });
|
|
174
|
+
}
|
|
175
|
+
await server.update({
|
|
176
|
+
name: parsed.name !== undefined ? parsed.name.trim() : server.name,
|
|
177
|
+
description: parsed.description !== undefined ? parsed.description : server.description,
|
|
178
|
+
transport,
|
|
179
|
+
command,
|
|
180
|
+
args,
|
|
181
|
+
env,
|
|
182
|
+
url,
|
|
183
|
+
headers,
|
|
184
|
+
isEnabled: parsed.isEnabled ?? server.isEnabled,
|
|
185
|
+
});
|
|
186
|
+
(0, mcpPromptCache_1.invalidatePromptMcps)(subscription.id);
|
|
187
|
+
void (0, mcpRuntime_1.invalidateMcpRuntimeForServer)(server.id);
|
|
188
|
+
res.json(formatServer(server));
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
logger.error('Error updating MCP server.', { error: err });
|
|
192
|
+
if (err instanceof zod_1.default.ZodError) {
|
|
193
|
+
return res.status(400).json({ error: 'Invalid MCP server data.' });
|
|
194
|
+
}
|
|
195
|
+
if (err?.name === 'SequelizeUniqueConstraintError') {
|
|
196
|
+
return res.status(409).json({ error: 'An MCP server with that name already exists.' });
|
|
197
|
+
}
|
|
198
|
+
res.status(500).json({ error: 'Failed to update MCP server.' });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
router.delete('/:id', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
202
|
+
const { logger, subscription } = res.locals;
|
|
203
|
+
const { id } = req.params;
|
|
204
|
+
try {
|
|
205
|
+
const server = await mcpServer_1.MCPServer.findOne({
|
|
206
|
+
where: { id, subscriptionId: subscription.id },
|
|
207
|
+
});
|
|
208
|
+
if (!server) {
|
|
209
|
+
return res.status(404).json({ error: 'MCP server not found.' });
|
|
210
|
+
}
|
|
211
|
+
await server.destroy();
|
|
212
|
+
(0, mcpPromptCache_1.invalidatePromptMcps)(subscription.id);
|
|
213
|
+
void (0, mcpRuntime_1.invalidateMcpRuntimeForServer)(server.id);
|
|
214
|
+
res.status(204).send();
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
logger.error('Error deleting MCP server.', { error: err });
|
|
218
|
+
res.status(500).json({ error: 'Failed to delete MCP server.' });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return router;
|
|
222
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MCPServer = void 0;
|
|
7
|
+
const sequelize_1 = require("sequelize");
|
|
8
|
+
const cuid_1 = __importDefault(require("cuid"));
|
|
9
|
+
const db_1 = require("../db");
|
|
10
|
+
const subscription_1 = require("./subscription");
|
|
11
|
+
class MCPServer extends sequelize_1.Model {
|
|
12
|
+
}
|
|
13
|
+
exports.MCPServer = MCPServer;
|
|
14
|
+
MCPServer.init({
|
|
15
|
+
id: {
|
|
16
|
+
type: sequelize_1.DataTypes.STRING,
|
|
17
|
+
primaryKey: true,
|
|
18
|
+
allowNull: false,
|
|
19
|
+
defaultValue: () => (0, cuid_1.default)(),
|
|
20
|
+
},
|
|
21
|
+
subscriptionId: {
|
|
22
|
+
type: sequelize_1.DataTypes.STRING,
|
|
23
|
+
allowNull: false,
|
|
24
|
+
field: 'subscription_id',
|
|
25
|
+
references: {
|
|
26
|
+
model: subscription_1.Subscription,
|
|
27
|
+
key: 'id',
|
|
28
|
+
},
|
|
29
|
+
onDelete: 'CASCADE',
|
|
30
|
+
onUpdate: 'CASCADE',
|
|
31
|
+
},
|
|
32
|
+
name: {
|
|
33
|
+
type: sequelize_1.DataTypes.STRING(100),
|
|
34
|
+
allowNull: false,
|
|
35
|
+
},
|
|
36
|
+
description: {
|
|
37
|
+
type: sequelize_1.DataTypes.STRING(500),
|
|
38
|
+
allowNull: true,
|
|
39
|
+
},
|
|
40
|
+
transport: {
|
|
41
|
+
type: sequelize_1.DataTypes.STRING(16),
|
|
42
|
+
allowNull: false,
|
|
43
|
+
defaultValue: 'stdio',
|
|
44
|
+
},
|
|
45
|
+
command: {
|
|
46
|
+
type: sequelize_1.DataTypes.STRING(500),
|
|
47
|
+
allowNull: true,
|
|
48
|
+
},
|
|
49
|
+
args: {
|
|
50
|
+
type: sequelize_1.DataTypes.JSON,
|
|
51
|
+
allowNull: false,
|
|
52
|
+
defaultValue: [],
|
|
53
|
+
},
|
|
54
|
+
env: {
|
|
55
|
+
type: sequelize_1.DataTypes.JSON,
|
|
56
|
+
allowNull: false,
|
|
57
|
+
defaultValue: {},
|
|
58
|
+
},
|
|
59
|
+
url: {
|
|
60
|
+
type: sequelize_1.DataTypes.STRING(1000),
|
|
61
|
+
allowNull: true,
|
|
62
|
+
},
|
|
63
|
+
headers: {
|
|
64
|
+
type: sequelize_1.DataTypes.JSON,
|
|
65
|
+
allowNull: false,
|
|
66
|
+
defaultValue: {},
|
|
67
|
+
},
|
|
68
|
+
isEnabled: {
|
|
69
|
+
type: sequelize_1.DataTypes.BOOLEAN,
|
|
70
|
+
allowNull: false,
|
|
71
|
+
defaultValue: true,
|
|
72
|
+
field: 'is_enabled',
|
|
73
|
+
},
|
|
74
|
+
lastConnectedAt: {
|
|
75
|
+
type: sequelize_1.DataTypes.DATE,
|
|
76
|
+
allowNull: true,
|
|
77
|
+
field: 'last_connected_at',
|
|
78
|
+
},
|
|
79
|
+
lastError: {
|
|
80
|
+
type: sequelize_1.DataTypes.TEXT,
|
|
81
|
+
allowNull: true,
|
|
82
|
+
field: 'last_error',
|
|
83
|
+
},
|
|
84
|
+
}, {
|
|
85
|
+
sequelize: db_1.sequelize,
|
|
86
|
+
tableName: 'mcp_servers',
|
|
87
|
+
modelName: 'MCPServer',
|
|
88
|
+
indexes: [
|
|
89
|
+
{
|
|
90
|
+
unique: true,
|
|
91
|
+
fields: ['subscription_id', 'name'],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
subscription_1.Subscription.hasMany(MCPServer, {
|
|
96
|
+
foreignKey: 'subscriptionId',
|
|
97
|
+
as: 'mcpServers',
|
|
98
|
+
});
|
|
99
|
+
MCPServer.belongsTo(subscription_1.Subscription, {
|
|
100
|
+
foreignKey: 'subscriptionId',
|
|
101
|
+
as: 'subscription',
|
|
102
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const showConfig_1 = require("./showConfig");
|
|
|
12
12
|
const setConfig_1 = require("./setConfig");
|
|
13
13
|
const grantBrowserAccess_1 = require("./grantBrowserAccess");
|
|
14
14
|
const scheduleJob_1 = require("./scheduleJob");
|
|
15
|
+
const mcpServer_1 = require("./mcpServer");
|
|
15
16
|
const program = new commander_1.Command();
|
|
16
17
|
program
|
|
17
18
|
.name('omnikey')
|
|
@@ -95,23 +96,62 @@ program
|
|
|
95
96
|
.action(async () => {
|
|
96
97
|
await (0, grantBrowserAccess_1.reopenBrowserDebugProfile)();
|
|
97
98
|
});
|
|
98
|
-
const scheduleCmd = program
|
|
99
|
-
.command('schedule')
|
|
100
|
-
.description('Manage scheduled prompt jobs');
|
|
99
|
+
const scheduleCmd = program.command('schedule').description('Manage scheduled prompt jobs');
|
|
101
100
|
scheduleCmd
|
|
102
101
|
.command('add')
|
|
103
102
|
.description('Add a new scheduled job')
|
|
104
|
-
.action(async () => {
|
|
103
|
+
.action(async () => {
|
|
104
|
+
await (0, scheduleJob_1.scheduleAdd)();
|
|
105
|
+
});
|
|
105
106
|
scheduleCmd
|
|
106
107
|
.command('list')
|
|
107
108
|
.description('List all scheduled jobs')
|
|
108
|
-
.action(async () => {
|
|
109
|
+
.action(async () => {
|
|
110
|
+
await (0, scheduleJob_1.scheduleList)();
|
|
111
|
+
});
|
|
109
112
|
scheduleCmd
|
|
110
113
|
.command('remove')
|
|
111
114
|
.description('Remove a scheduled job')
|
|
112
|
-
.action(async () => {
|
|
115
|
+
.action(async () => {
|
|
116
|
+
await (0, scheduleJob_1.scheduleRemove)();
|
|
117
|
+
});
|
|
113
118
|
scheduleCmd
|
|
114
119
|
.command('run-now <id>')
|
|
115
120
|
.description('Immediately run a scheduled job by ID')
|
|
116
|
-
.action(async (id) => {
|
|
121
|
+
.action(async (id) => {
|
|
122
|
+
await (0, scheduleJob_1.scheduleRunNow)(id);
|
|
123
|
+
});
|
|
124
|
+
const mcpCmd = program
|
|
125
|
+
.command('mcp')
|
|
126
|
+
.description('Manage MCP (Model Context Protocol) servers available to the agent');
|
|
127
|
+
mcpCmd
|
|
128
|
+
.command('add')
|
|
129
|
+
.description('Install a new MCP server')
|
|
130
|
+
.action(async () => {
|
|
131
|
+
await (0, mcpServer_1.mcpAdd)();
|
|
132
|
+
});
|
|
133
|
+
mcpCmd
|
|
134
|
+
.command('list')
|
|
135
|
+
.description('List installed MCP servers')
|
|
136
|
+
.action(async () => {
|
|
137
|
+
await (0, mcpServer_1.mcpList)();
|
|
138
|
+
});
|
|
139
|
+
mcpCmd
|
|
140
|
+
.command('remove')
|
|
141
|
+
.description('Remove an installed MCP server')
|
|
142
|
+
.action(async () => {
|
|
143
|
+
await (0, mcpServer_1.mcpRemove)();
|
|
144
|
+
});
|
|
145
|
+
mcpCmd
|
|
146
|
+
.command('toggle <id>')
|
|
147
|
+
.description('Toggle an MCP server enabled/disabled by ID')
|
|
148
|
+
.action(async (id) => {
|
|
149
|
+
await (0, mcpServer_1.mcpToggle)(id);
|
|
150
|
+
});
|
|
151
|
+
mcpCmd
|
|
152
|
+
.command('update <id>')
|
|
153
|
+
.description('Update an existing MCP server by ID')
|
|
154
|
+
.action(async (id) => {
|
|
155
|
+
await (0, mcpServer_1.mcpUpdate)(id);
|
|
156
|
+
});
|
|
117
157
|
program.parseAsync(process.argv);
|