securenow 7.4.0 → 7.5.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/NPM_README.md +40 -24
- package/README.md +22 -6
- package/SKILL-API.md +19 -16
- package/SKILL-CLI.md +16 -5
- package/cli/diagnostics.js +21 -2
- package/cli/init.js +94 -51
- package/cli/ui.js +22 -12
- package/cli.js +14 -9
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +21 -18
- package/docs/API-KEYS-GUIDE.md +2 -2
- package/docs/ENVIRONMENT-VARIABLES.md +10 -9
- package/docs/FIREWALL-GUIDE.md +5 -4
- package/docs/MCP-GUIDE.md +50 -0
- package/mcp/catalog.js +770 -0
- package/mcp/server.js +238 -0
- package/nextjs-auto-capture.d.ts +1 -1
- package/package.json +10 -2
package/mcp/server.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const config = require('../cli/config');
|
|
6
|
+
const { api } = require('../cli/client');
|
|
7
|
+
const pkg = require('../package.json');
|
|
8
|
+
const {
|
|
9
|
+
RESOURCES,
|
|
10
|
+
getTool,
|
|
11
|
+
listTools,
|
|
12
|
+
listResources,
|
|
13
|
+
listPrompts,
|
|
14
|
+
promptMessages,
|
|
15
|
+
assertConfirmed,
|
|
16
|
+
buildApiRequest,
|
|
17
|
+
resourceContent,
|
|
18
|
+
jsonText,
|
|
19
|
+
maskSecret,
|
|
20
|
+
sanitizeArgs,
|
|
21
|
+
} = require('./catalog');
|
|
22
|
+
|
|
23
|
+
const SERVER_NAME = 'securenow';
|
|
24
|
+
const PROTOCOL_VERSION = '2025-06-18';
|
|
25
|
+
|
|
26
|
+
function write(message) {
|
|
27
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ok(id, result) {
|
|
31
|
+
write({ jsonrpc: '2.0', id, result });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(id, code, message, data) {
|
|
35
|
+
write({
|
|
36
|
+
jsonrpc: '2.0',
|
|
37
|
+
id,
|
|
38
|
+
error: {
|
|
39
|
+
code,
|
|
40
|
+
message,
|
|
41
|
+
...(data ? { data } : {}),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function bearerToken() {
|
|
47
|
+
return config.getToken() || config.getApiKey();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function localAuthStatus() {
|
|
51
|
+
const cfg = config.loadConfig();
|
|
52
|
+
const app = config.getApp();
|
|
53
|
+
const token = config.getToken();
|
|
54
|
+
const apiKey = config.getApiKey();
|
|
55
|
+
const hasBearer = !!(token || apiKey);
|
|
56
|
+
return {
|
|
57
|
+
authenticated: hasBearer,
|
|
58
|
+
sessionTokenAvailable: !!token,
|
|
59
|
+
apiKeyAvailable: !!apiKey,
|
|
60
|
+
authSource: config.getAuthSource(),
|
|
61
|
+
apiUrl: config.getApiUrl(),
|
|
62
|
+
appUrl: config.getAppUrl(),
|
|
63
|
+
defaultApp: config.getDefaultApp(),
|
|
64
|
+
app: app || null,
|
|
65
|
+
token: token ? maskSecret(token) : null,
|
|
66
|
+
apiKey: apiKey ? maskSecret(apiKey) : null,
|
|
67
|
+
config: {
|
|
68
|
+
apiUrl: cfg.apiUrl,
|
|
69
|
+
appUrl: cfg.appUrl,
|
|
70
|
+
defaultApp: cfg.defaultApp,
|
|
71
|
+
output: cfg.output,
|
|
72
|
+
},
|
|
73
|
+
nextStep: token
|
|
74
|
+
? null
|
|
75
|
+
: apiKey
|
|
76
|
+
? 'Using a scoped API key. Run `npx securenow login` for account-wide MCP tools.'
|
|
77
|
+
: 'Run `npx securenow login` from the project root.',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function callApiTool(tool, args) {
|
|
82
|
+
assertConfirmed(tool, args);
|
|
83
|
+
|
|
84
|
+
if (tool.name === 'securenow_auth_status') {
|
|
85
|
+
return localAuthStatus();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (tool.localOnly) {
|
|
89
|
+
throw new Error(`${tool.name} is only available in the local MCP server.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const token = bearerToken();
|
|
93
|
+
if (!token) {
|
|
94
|
+
throw new Error('Not authenticated. Run `npx securenow login` from the project root, then retry.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const request = buildApiRequest(tool, args);
|
|
98
|
+
const options = {
|
|
99
|
+
token,
|
|
100
|
+
...(Object.keys(request.query || {}).length > 0 ? { query: request.query } : {}),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (request.method === 'GET') return api.get(request.endpoint, options);
|
|
104
|
+
if (request.method === 'POST') return api.post(request.endpoint, request.body || {}, options);
|
|
105
|
+
if (request.method === 'PUT') return api.put(request.endpoint, request.body || {}, options);
|
|
106
|
+
if (request.method === 'PATCH') return api.patch(request.endpoint, request.body || {}, options);
|
|
107
|
+
if (request.method === 'DELETE') return api.delete(request.endpoint, options);
|
|
108
|
+
|
|
109
|
+
throw new Error(`Unsupported method for ${tool.name}: ${request.method}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function asToolResult(result) {
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: typeof result === 'string' ? result : jsonText(result),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
structuredContent: typeof result === 'object' && result !== null ? result : { value: result },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readResource(uri) {
|
|
125
|
+
const resource = RESOURCES.find((item) => item.uri === uri);
|
|
126
|
+
if (!resource) throw new Error(`Unknown resource: ${uri}`);
|
|
127
|
+
|
|
128
|
+
const text = resourceContent(resource, {
|
|
129
|
+
projectConfig: () => jsonText(localAuthStatus()),
|
|
130
|
+
toolsCatalog: () => jsonText(listTools().map((tool) => ({
|
|
131
|
+
name: tool.name,
|
|
132
|
+
description: tool.description,
|
|
133
|
+
readOnly: tool.annotations?.readOnlyHint === true,
|
|
134
|
+
destructive: tool.annotations?.destructiveHint === true,
|
|
135
|
+
}))),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
contents: [
|
|
140
|
+
{
|
|
141
|
+
uri: resource.uri,
|
|
142
|
+
mimeType: resource.mimeType,
|
|
143
|
+
text,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function handleRequest(message) {
|
|
150
|
+
const { id, method, params = {} } = message;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (method === 'initialize') {
|
|
154
|
+
return ok(id, {
|
|
155
|
+
protocolVersion: params.protocolVersion || PROTOCOL_VERSION,
|
|
156
|
+
capabilities: {
|
|
157
|
+
tools: {},
|
|
158
|
+
resources: {},
|
|
159
|
+
prompts: {},
|
|
160
|
+
},
|
|
161
|
+
serverInfo: {
|
|
162
|
+
name: SERVER_NAME,
|
|
163
|
+
version: pkg.version,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (method === 'ping') return ok(id, {});
|
|
169
|
+
if (method === 'tools/list') return ok(id, { tools: listTools() });
|
|
170
|
+
if (method === 'resources/list') return ok(id, { resources: listResources() });
|
|
171
|
+
if (method === 'prompts/list') return ok(id, { prompts: listPrompts() });
|
|
172
|
+
|
|
173
|
+
if (method === 'resources/read') {
|
|
174
|
+
if (!params.uri) throw new Error('resources/read requires params.uri');
|
|
175
|
+
return ok(id, readResource(params.uri));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (method === 'prompts/get') {
|
|
179
|
+
if (!params.name) throw new Error('prompts/get requires params.name');
|
|
180
|
+
return ok(id, {
|
|
181
|
+
description: listPrompts().find((prompt) => prompt.name === params.name)?.description || '',
|
|
182
|
+
messages: promptMessages(params.name, params.arguments || {}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (method === 'tools/call') {
|
|
187
|
+
const name = params.name;
|
|
188
|
+
const args = params.arguments || {};
|
|
189
|
+
const tool = getTool(name);
|
|
190
|
+
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
191
|
+
|
|
192
|
+
const result = await callApiTool(tool, args);
|
|
193
|
+
return ok(id, asToolResult({
|
|
194
|
+
tool: name,
|
|
195
|
+
arguments: sanitizeArgs(args),
|
|
196
|
+
result,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return fail(id, -32601, `Method not found: ${method}`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
return fail(id, -32000, err.message || 'SecureNow MCP request failed');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function handleMessage(message) {
|
|
207
|
+
if (Array.isArray(message)) {
|
|
208
|
+
for (const item of message) handleMessage(item);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!message || message.jsonrpc !== '2.0') {
|
|
213
|
+
return fail(null, -32600, 'Invalid JSON-RPC message');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Notifications have no id and must not receive a response.
|
|
217
|
+
if (message.id == null) return;
|
|
218
|
+
|
|
219
|
+
handleRequest(message).catch((err) => {
|
|
220
|
+
fail(message.id, -32603, err.message || 'Internal error');
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const rl = readline.createInterface({
|
|
225
|
+
input: process.stdin,
|
|
226
|
+
crlfDelay: Infinity,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
rl.on('line', (line) => {
|
|
230
|
+
if (!line.trim()) return;
|
|
231
|
+
try {
|
|
232
|
+
handleMessage(JSON.parse(line));
|
|
233
|
+
} catch (err) {
|
|
234
|
+
fail(null, -32700, 'Parse error');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
rl.on('close', () => process.exit(0));
|
package/nextjs-auto-capture.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*
|
|
18
18
|
* Environment Variables:
|
|
19
|
-
* - SECURENOW_CAPTURE_BODY=
|
|
19
|
+
* - SECURENOW_CAPTURE_BODY=0 - Disable body capture (default: enabled)
|
|
20
20
|
* - SECURENOW_MAX_BODY_SIZE=10240 - Max body size in bytes (default: 10KB)
|
|
21
21
|
* - SECURENOW_SENSITIVE_FIELDS=field1,field2 - Additional sensitive fields to redact
|
|
22
22
|
*
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.1",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "register.js",
|
|
7
7
|
"types": "register.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"securenow": "cli.js"
|
|
9
|
+
"securenow": "cli.js",
|
|
10
|
+
"securenow-mcp": "mcp/server.js"
|
|
10
11
|
},
|
|
11
12
|
"scripts": {
|
|
12
13
|
"postinstall": "node postinstall.js || exit 0"
|
|
@@ -97,6 +98,12 @@
|
|
|
97
98
|
},
|
|
98
99
|
"./app-config": {
|
|
99
100
|
"default": "./app-config.js"
|
|
101
|
+
},
|
|
102
|
+
"./mcp": {
|
|
103
|
+
"default": "./mcp/server.js"
|
|
104
|
+
},
|
|
105
|
+
"./mcp/catalog": {
|
|
106
|
+
"default": "./mcp/catalog.js"
|
|
100
107
|
}
|
|
101
108
|
},
|
|
102
109
|
"files": [
|
|
@@ -119,6 +126,7 @@
|
|
|
119
126
|
"nuxt-server-plugin.mjs",
|
|
120
127
|
"cli.js",
|
|
121
128
|
"cli/",
|
|
129
|
+
"mcp/",
|
|
122
130
|
"free-trial-banner.js",
|
|
123
131
|
"resolve-ip.js",
|
|
124
132
|
"cidr.js",
|