@kylewadegrove/cutline-mcp-cli 0.4.2 → 0.5.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/Dockerfile +11 -0
- package/README.md +177 -107
- package/dist/auth/callback.js +30 -32
- package/dist/auth/keychain.js +7 -15
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +246 -0
- package/dist/commands/login.js +39 -45
- package/dist/commands/logout.js +13 -19
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +38 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +255 -0
- package/dist/commands/status.js +29 -35
- package/dist/commands/upgrade.js +44 -38
- package/dist/index.js +38 -14
- package/dist/servers/chunk-7FHM2GD3.js +5836 -0
- package/dist/servers/chunk-IVWF7VYZ.js +10086 -0
- package/dist/servers/chunk-JBJYSV4P.js +139 -0
- package/dist/servers/chunk-KMUSQOTJ.js +47 -0
- package/dist/servers/chunk-PD2HN2R5.js +908 -0
- package/dist/servers/chunk-PU7TL6S3.js +91 -0
- package/dist/servers/chunk-TGSEURMN.js +46 -0
- package/dist/servers/chunk-UBBAYTW3.js +946 -0
- package/dist/servers/cutline-server.js +11512 -0
- package/dist/servers/exploration-server.js +1030 -0
- package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
- package/dist/servers/integrations-server.js +121 -0
- package/dist/servers/output-server.js +120 -0
- package/dist/servers/pipeline-O5GJPNR4.js +20 -0
- package/dist/servers/premortem-handoff-XT4K3YDJ.js +10 -0
- package/dist/servers/premortem-server.js +958 -0
- package/dist/servers/score-history-HO5KRVGC.js +6 -0
- package/dist/servers/tools-server.js +291 -0
- package/dist/utils/config-store.js +13 -21
- package/dist/utils/config.js +2 -6
- package/mcpb/manifest.json +77 -0
- package/package.json +55 -9
- package/server.json +42 -0
- package/smithery.yaml +10 -0
- package/src/auth/callback.ts +0 -102
- package/src/auth/keychain.ts +0 -16
- package/src/commands/login.ts +0 -202
- package/src/commands/logout.ts +0 -30
- package/src/commands/status.ts +0 -153
- package/src/commands/upgrade.ts +0 -121
- package/src/index.ts +0 -40
- package/src/utils/config-store.ts +0 -46
- package/src/utils/config.ts +0 -65
- package/tsconfig.json +0 -22
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isWriteTool
|
|
4
|
+
} from "./chunk-KMUSQOTJ.js";
|
|
5
|
+
import {
|
|
6
|
+
applyEditsLogic,
|
|
7
|
+
chatWithPersona,
|
|
8
|
+
generateChatSuggestion,
|
|
9
|
+
generateTrialRun,
|
|
10
|
+
getPersona,
|
|
11
|
+
getWikiMarkdown,
|
|
12
|
+
listPersonas,
|
|
13
|
+
saveWikiMarkdown
|
|
14
|
+
} from "./chunk-IVWF7VYZ.js";
|
|
15
|
+
import {
|
|
16
|
+
guardBoundary,
|
|
17
|
+
guardOutput,
|
|
18
|
+
initFirebase,
|
|
19
|
+
mapErrorToMcp,
|
|
20
|
+
requirePremiumWithAutoAuth,
|
|
21
|
+
resolveAuthContext,
|
|
22
|
+
validateAuth,
|
|
23
|
+
validateRequestSize,
|
|
24
|
+
withPerfTracking
|
|
25
|
+
} from "./chunk-PD2HN2R5.js";
|
|
26
|
+
import "./chunk-7FHM2GD3.js";
|
|
27
|
+
import "./chunk-JBJYSV4P.js";
|
|
28
|
+
|
|
29
|
+
// ../mcp/dist/mcp/src/tools-server.js
|
|
30
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
31
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
32
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
33
|
+
var server = new Server({
|
|
34
|
+
name: "cutline-tools",
|
|
35
|
+
version: "0.1.0"
|
|
36
|
+
}, {
|
|
37
|
+
capabilities: {
|
|
38
|
+
tools: {}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
42
|
+
return {
|
|
43
|
+
tools: [
|
|
44
|
+
{
|
|
45
|
+
name: "trial_generate",
|
|
46
|
+
description: "Generate a trial run response",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
prompt: { type: "string" }
|
|
51
|
+
},
|
|
52
|
+
required: ["prompt"]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "agent_chat",
|
|
57
|
+
description: "Get chat suggestions for wiki editing",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
prompt: { type: "string" },
|
|
62
|
+
wikiMarkdown: { type: "string" },
|
|
63
|
+
auth_token: { type: "string" }
|
|
64
|
+
},
|
|
65
|
+
required: ["prompt"]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "wiki_load",
|
|
70
|
+
description: "Load wiki markdown",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
projectId: { type: "string" },
|
|
75
|
+
auth_token: { type: "string" }
|
|
76
|
+
},
|
|
77
|
+
required: ["projectId"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "wiki_save",
|
|
82
|
+
description: "Save wiki markdown",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
projectId: { type: "string" },
|
|
87
|
+
markdown: { type: "string" },
|
|
88
|
+
auth_token: { type: "string" }
|
|
89
|
+
},
|
|
90
|
+
required: ["projectId", "markdown"]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "wiki_apply_edits",
|
|
95
|
+
description: "Apply edits to wiki",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
edits: { type: "array", items: { type: "object" } },
|
|
100
|
+
auth_token: { type: "string" }
|
|
101
|
+
},
|
|
102
|
+
required: ["edits"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "personas_list",
|
|
107
|
+
description: "List personas for the authenticated user, optionally filtered by productId",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
productId: { type: "string", description: "Optional product ID to filter personas" },
|
|
112
|
+
auth_token: { type: "string" }
|
|
113
|
+
},
|
|
114
|
+
required: ["auth_token"]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "personas_get",
|
|
119
|
+
description: "Get a specific persona by ID",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
personaId: { type: "string" },
|
|
124
|
+
auth_token: { type: "string" }
|
|
125
|
+
},
|
|
126
|
+
required: ["personaId", "auth_token"]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "personas_chat",
|
|
131
|
+
description: "Chat with a persona. The persona object should include name, description, role, segment, demographics, personality, and behaviors.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
persona: {
|
|
136
|
+
type: "object",
|
|
137
|
+
description: "Persona object with name, description, role, segment, demographics, personality, behaviors"
|
|
138
|
+
},
|
|
139
|
+
userMessage: { type: "string", description: "The user's message to the persona" },
|
|
140
|
+
product: {
|
|
141
|
+
type: "object",
|
|
142
|
+
description: "Optional product context (name, brief, etc.)"
|
|
143
|
+
},
|
|
144
|
+
conversationHistory: {
|
|
145
|
+
type: "array",
|
|
146
|
+
items: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
role: { type: "string", enum: ["user", "assistant"] },
|
|
150
|
+
content: { type: "string" }
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
description: "Optional conversation history"
|
|
154
|
+
},
|
|
155
|
+
auth_token: { type: "string" }
|
|
156
|
+
},
|
|
157
|
+
required: ["persona", "userMessage", "auth_token"]
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "podcast_get_participants",
|
|
162
|
+
description: "Get podcast-ready introductions for all personas in a product. Returns taglines, intro scripts, signature traits, and voice settings for each persona. Use this to have Cutline introduce podcast guests.",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
productId: { type: "string", description: "The product ID to get personas for" },
|
|
167
|
+
format: {
|
|
168
|
+
type: "string",
|
|
169
|
+
enum: ["json", "script", "bullets"],
|
|
170
|
+
description: "Output format: 'json' for structured data, 'script' for host-readable text, 'bullets' for quick reference"
|
|
171
|
+
},
|
|
172
|
+
auth_token: { type: "string" }
|
|
173
|
+
},
|
|
174
|
+
required: ["productId", "auth_token"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
181
|
+
try {
|
|
182
|
+
const { name, arguments: rawArgs } = request.params;
|
|
183
|
+
if (!rawArgs)
|
|
184
|
+
throw new McpError(ErrorCode.InvalidParams, "Missing arguments");
|
|
185
|
+
validateRequestSize(rawArgs);
|
|
186
|
+
const { args } = guardBoundary(name, rawArgs);
|
|
187
|
+
initFirebase();
|
|
188
|
+
const rawResponse = await withPerfTracking(name, async () => {
|
|
189
|
+
if (isWriteTool(name)) {
|
|
190
|
+
const peekToken = args.auth_token;
|
|
191
|
+
if (peekToken) {
|
|
192
|
+
const peekDecoded = await validateAuth(peekToken).catch(() => null);
|
|
193
|
+
if (peekDecoded && peekDecoded.accountType === "agent") {
|
|
194
|
+
throw new McpError(ErrorCode.InvalidRequest, "This is a read-only agent account. Write operations require the owner account.");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
switch (name) {
|
|
199
|
+
case "trial_generate": {
|
|
200
|
+
const { prompt } = args;
|
|
201
|
+
const text = await generateTrialRun(prompt);
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, text }) }]
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
case "agent_chat": {
|
|
207
|
+
const { prompt, wikiMarkdown, auth_token } = args;
|
|
208
|
+
await requirePremiumWithAutoAuth(auth_token);
|
|
209
|
+
const text = await generateChatSuggestion(prompt, wikiMarkdown);
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: JSON.stringify({ text }) }]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
case "wiki_load": {
|
|
215
|
+
const { projectId, auth_token } = args;
|
|
216
|
+
await requirePremiumWithAutoAuth(auth_token);
|
|
217
|
+
const markdown = await getWikiMarkdown(projectId);
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text: JSON.stringify({ markdown }) }]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
case "wiki_save": {
|
|
223
|
+
const { projectId, markdown, auth_token } = args;
|
|
224
|
+
const decoded = await requirePremiumWithAutoAuth(auth_token);
|
|
225
|
+
await saveWikiMarkdown(projectId, markdown, decoded?.uid);
|
|
226
|
+
return {
|
|
227
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true }) }]
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
case "wiki_apply_edits": {
|
|
231
|
+
const { edits, auth_token } = args;
|
|
232
|
+
await requirePremiumWithAutoAuth(auth_token);
|
|
233
|
+
const result = await applyEditsLogic(edits);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
case "personas_list": {
|
|
239
|
+
const { productId, auth_token } = args;
|
|
240
|
+
const normalizedAuthToken = auth_token && auth_token !== "auto" && auth_token.trim() !== "" ? auth_token : void 0;
|
|
241
|
+
const { effectiveUid } = await resolveAuthContext(normalizedAuthToken);
|
|
242
|
+
const personas = await listPersonas(effectiveUid, productId);
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: "text", text: JSON.stringify({ personas }) }]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
case "personas_get": {
|
|
248
|
+
const { personaId, auth_token } = args;
|
|
249
|
+
const normalizedAuthToken = auth_token && auth_token !== "auto" && auth_token.trim() !== "" ? auth_token : void 0;
|
|
250
|
+
const { effectiveUid } = await resolveAuthContext(normalizedAuthToken);
|
|
251
|
+
const persona = await getPersona(effectiveUid, personaId);
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: JSON.stringify({ persona }) }]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
case "personas_chat": {
|
|
257
|
+
const { persona, userMessage, product, conversationHistory, auth_token } = args;
|
|
258
|
+
const normalizedAuthToken = auth_token && auth_token !== "auto" && auth_token.trim() !== "" ? auth_token : void 0;
|
|
259
|
+
await requirePremiumWithAutoAuth(normalizedAuthToken);
|
|
260
|
+
const result = await chatWithPersona(persona, userMessage, product, conversationHistory);
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
case "podcast_get_participants": {
|
|
266
|
+
const { productId, format, auth_token } = args;
|
|
267
|
+
const normalizedAuthToken = auth_token && auth_token !== "auto" && auth_token.trim() !== "" ? auth_token : void 0;
|
|
268
|
+
const { effectiveUid } = await resolveAuthContext(normalizedAuthToken);
|
|
269
|
+
const result = await (void 0)(effectiveUid, productId, format || "json");
|
|
270
|
+
return {
|
|
271
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return guardOutput(name, rawResponse);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw mapErrorToMcp(error, { tool: request.params.name });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
async function run() {
|
|
284
|
+
const transport = new StdioServerTransport();
|
|
285
|
+
await server.connect(transport);
|
|
286
|
+
console.error("Cutline Tools MCP Server running on stdio");
|
|
287
|
+
}
|
|
288
|
+
run().catch((error) => {
|
|
289
|
+
console.error("Fatal error running server:", error);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.saveConfig = saveConfig;
|
|
7
|
-
exports.loadConfig = loadConfig;
|
|
8
|
-
exports.getRefreshTokenFromFile = getRefreshTokenFromFile;
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const os_1 = __importDefault(require("os"));
|
|
12
|
-
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.cutline-mcp');
|
|
13
|
-
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.cutline-mcp');
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
14
6
|
function ensureConfigDir() {
|
|
15
|
-
if (!
|
|
16
|
-
|
|
7
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
8
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
17
9
|
}
|
|
18
10
|
}
|
|
19
|
-
function saveConfig(config) {
|
|
11
|
+
export function saveConfig(config) {
|
|
20
12
|
ensureConfigDir();
|
|
21
13
|
const current = loadConfig();
|
|
22
14
|
const newConfig = { ...current, ...config };
|
|
23
15
|
// Remove legacy fields that are no longer stored (e.g. firebaseApiKey)
|
|
24
16
|
// to prevent stale values from causing cross-project auth mismatches
|
|
25
17
|
delete newConfig.firebaseApiKey;
|
|
26
|
-
|
|
18
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2), { mode: 0o600 });
|
|
27
19
|
}
|
|
28
|
-
function loadConfig() {
|
|
20
|
+
export function loadConfig() {
|
|
29
21
|
try {
|
|
30
|
-
if (
|
|
31
|
-
const content =
|
|
22
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
23
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
32
24
|
return JSON.parse(content);
|
|
33
25
|
}
|
|
34
26
|
}
|
|
@@ -37,7 +29,7 @@ function loadConfig() {
|
|
|
37
29
|
}
|
|
38
30
|
return {};
|
|
39
31
|
}
|
|
40
|
-
function getRefreshTokenFromFile() {
|
|
32
|
+
export function getRefreshTokenFromFile() {
|
|
41
33
|
const config = loadConfig();
|
|
42
34
|
return config.refreshToken || null;
|
|
43
35
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getConfig = getConfig;
|
|
4
|
-
exports.fetchFirebaseApiKey = fetchFirebaseApiKey;
|
|
5
1
|
/**
|
|
6
2
|
* Get static config (URLs only, no API key)
|
|
7
3
|
*/
|
|
8
|
-
function getConfig(options = {}) {
|
|
4
|
+
export function getConfig(options = {}) {
|
|
9
5
|
if (options.staging) {
|
|
10
6
|
return {
|
|
11
7
|
AUTH_URL: process.env.CUTLINE_AUTH_URL || 'https://cutline-staging.web.app/mcp-auth',
|
|
@@ -24,7 +20,7 @@ function getConfig(options = {}) {
|
|
|
24
20
|
* This avoids hardcoding API keys in source code.
|
|
25
21
|
* Falls back to environment variables if fetch fails.
|
|
26
22
|
*/
|
|
27
|
-
async function fetchFirebaseApiKey(options = {}) {
|
|
23
|
+
export async function fetchFirebaseApiKey(options = {}) {
|
|
28
24
|
// First check environment variables
|
|
29
25
|
const envKey = process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
|
|
30
26
|
if (envKey) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.3",
|
|
3
|
+
"name": "cutline-mcp",
|
|
4
|
+
"display_name": "Cutline — Engineering Guardrails",
|
|
5
|
+
"version": "0.5.0",
|
|
6
|
+
"description": "Security, reliability, and scalability constraints for your coding agent. Free code audits with 9 compliance frameworks built in.",
|
|
7
|
+
"long_description": "Cutline is a guardrail middleware for AI coding agents. It extracts non-functional requirements (security, scalability, reliability) from your ideas and injects them as structured constraints into your agent's context. Includes free engineering audits (3/month), SOC 2 / PCI-DSS / HIPAA / GDPR / OWASP LLM Top 10 compliance frameworks, pre-mortem risk analysis, and a Red-Green-Refactor workflow for systematic remediation.",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Cutline",
|
|
10
|
+
"url": "https://thecutline.ai"
|
|
11
|
+
},
|
|
12
|
+
"repository": "https://github.com/kylewadegrove/cutline",
|
|
13
|
+
"homepage": "https://thecutline.ai",
|
|
14
|
+
"documentation": "https://thecutline.ai/docs/setup",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"security",
|
|
18
|
+
"guardrail",
|
|
19
|
+
"vibecoding",
|
|
20
|
+
"code-audit",
|
|
21
|
+
"compliance",
|
|
22
|
+
"soc2",
|
|
23
|
+
"nfr",
|
|
24
|
+
"constraint",
|
|
25
|
+
"pre-mortem"
|
|
26
|
+
],
|
|
27
|
+
"server": {
|
|
28
|
+
"type": "node",
|
|
29
|
+
"entry_point": "server/cutline-server.js",
|
|
30
|
+
"mcp_config": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@kylewadegrove/cutline-mcp-cli@latest", "serve", "constraints"]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"tools": [
|
|
36
|
+
{
|
|
37
|
+
"name": "engineering_audit",
|
|
38
|
+
"description": "Free security, reliability, and scalability scan of your codebase (3/month)"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "code_audit",
|
|
42
|
+
"description": "Premium deep code audit with RGR remediation plan"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "constraints_auto",
|
|
46
|
+
"description": "Auto-inject relevant constraints based on file context"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "constraints_query",
|
|
50
|
+
"description": "Query product constraints by keyword"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "exploration_start",
|
|
54
|
+
"description": "Start a guided product idea exploration"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "premortem_run",
|
|
58
|
+
"description": "Run a full pre-mortem risk analysis on a product idea"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "graph_metrics",
|
|
62
|
+
"description": "Get engineering readiness scores and NFR coverage"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"user_config": {
|
|
66
|
+
"properties": {
|
|
67
|
+
"CUTLINE_ENVIRONMENT": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Environment: 'production' or 'staging'",
|
|
70
|
+
"default": "production"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"compatibility": {
|
|
75
|
+
"clients": ["claude-desktop"]
|
|
76
|
+
}
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,36 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kylewadegrove/cutline-mcp-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"bin": {
|
|
7
8
|
"cutline-mcp": "./dist/index.js"
|
|
8
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/**/*",
|
|
12
|
+
"README.md",
|
|
13
|
+
"server.json",
|
|
14
|
+
"smithery.yaml",
|
|
15
|
+
"Dockerfile",
|
|
16
|
+
"mcpb/manifest.json"
|
|
17
|
+
],
|
|
9
18
|
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
19
|
+
"build:cli": "tsc",
|
|
20
|
+
"build:servers": "node scripts/bundle-servers.mjs",
|
|
21
|
+
"build": "npm run build:cli && npm run build:servers",
|
|
22
|
+
"build:mcpb": "bash scripts/build-mcpb.sh",
|
|
11
23
|
"dev": "tsc --watch",
|
|
12
|
-
"start": "node dist/index.js"
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
13
26
|
},
|
|
27
|
+
"mcpName": "ai.thecutline/cutline-mcp",
|
|
14
28
|
"keywords": [
|
|
15
29
|
"cutline",
|
|
16
30
|
"mcp",
|
|
31
|
+
"mcp-server",
|
|
32
|
+
"model-context-protocol",
|
|
17
33
|
"cli",
|
|
18
|
-
"
|
|
34
|
+
"security",
|
|
35
|
+
"guardrail",
|
|
36
|
+
"vibecoding",
|
|
37
|
+
"cursor",
|
|
38
|
+
"claude",
|
|
39
|
+
"windsurf",
|
|
40
|
+
"code-audit",
|
|
41
|
+
"engineering-audit",
|
|
42
|
+
"nfr",
|
|
43
|
+
"constraint",
|
|
44
|
+
"soc2",
|
|
45
|
+
"hipaa",
|
|
46
|
+
"pci-dss",
|
|
47
|
+
"gdpr",
|
|
48
|
+
"owasp",
|
|
49
|
+
"pre-mortem",
|
|
50
|
+
"ai-safety",
|
|
51
|
+
"cursor-security"
|
|
19
52
|
],
|
|
20
53
|
"author": "Cutline",
|
|
21
54
|
"license": "MIT",
|
|
22
55
|
"dependencies": {
|
|
56
|
+
"@google-cloud/storage": "^7.7.0",
|
|
57
|
+
"@google-cloud/vertexai": "^1.9.2",
|
|
58
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
59
|
+
"@pdf-lib/fontkit": "^1.1.1",
|
|
60
|
+
"chalk": "^4.1.2",
|
|
23
61
|
"commander": "^11.1.0",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
62
|
+
"customerio-node": "^4.0.0",
|
|
63
|
+
"dotenv": "^16.4.5",
|
|
26
64
|
"express": "^4.18.2",
|
|
27
65
|
"firebase-admin": "^12.0.0",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
66
|
+
"firebase-functions": "^6.3.0",
|
|
67
|
+
"google-auth-library": "^9.14.2",
|
|
68
|
+
"keytar": "^7.9.0",
|
|
69
|
+
"node-fetch": "^3.3.2",
|
|
70
|
+
"open": "^9.1.0",
|
|
71
|
+
"ora": "^5.4.1",
|
|
72
|
+
"pdf-lib": "^1.17.1",
|
|
73
|
+
"stripe": "^14.20.0",
|
|
74
|
+
"zod": "^3.23.8"
|
|
30
75
|
},
|
|
31
76
|
"devDependencies": {
|
|
32
77
|
"@types/express": "^4.17.21",
|
|
33
78
|
"@types/node": "^20.0.0",
|
|
79
|
+
"esbuild": "^0.25.0",
|
|
34
80
|
"typescript": "^5.3.0"
|
|
35
81
|
},
|
|
36
82
|
"engines": {
|
package/server.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "ai.thecutline/cutline-mcp",
|
|
4
|
+
"title": "Cutline — Engineering Guardrails for Vibecoding",
|
|
5
|
+
"description": "Security, reliability, and scalability constraints for your coding agent. Free code audits, compliance frameworks, and pre-mortem analysis.",
|
|
6
|
+
"version": "0.5.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/kylewadegrove/cutline",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"websiteUrl": "https://thecutline.ai",
|
|
12
|
+
"icons": {
|
|
13
|
+
"light": "https://thecutline.ai/assets/brand/cutline-icon-light.png",
|
|
14
|
+
"dark": "https://thecutline.ai/assets/brand/cutline-icon-dark.png"
|
|
15
|
+
},
|
|
16
|
+
"packages": [
|
|
17
|
+
{
|
|
18
|
+
"registryType": "npm",
|
|
19
|
+
"identifier": "@kylewadegrove/cutline-mcp-cli",
|
|
20
|
+
"version": "0.5.0",
|
|
21
|
+
"transport": {
|
|
22
|
+
"type": "stdio"
|
|
23
|
+
},
|
|
24
|
+
"environment_variables": [
|
|
25
|
+
{
|
|
26
|
+
"name": "CUTLINE_AUTH",
|
|
27
|
+
"description": "Authentication is handled via `cutline-mcp login` (stored in keychain). No manual env vars needed.",
|
|
28
|
+
"required": false
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"_meta": {
|
|
34
|
+
"io.modelcontextprotocol.registry/publisher-provided": {
|
|
35
|
+
"categories": ["security", "infrastructure", "developer-tools"],
|
|
36
|
+
"compliance_frameworks": ["SOC 2", "PCI-DSS", "HIPAA", "GDPR/CCPA", "OWASP LLM Top 10", "FedRAMP", "GLBA", "FERPA/COPPA"],
|
|
37
|
+
"free_tier": true,
|
|
38
|
+
"free_tools": ["engineering_audit", "exploration_start", "exploration_chat", "exploration_graduate", "llm_status", "perf_status"],
|
|
39
|
+
"tool_count": 54
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Smithery registry configuration for Cutline MCP
|
|
2
|
+
# https://smithery.ai/docs/publishing
|
|
3
|
+
|
|
4
|
+
runtime: "container"
|
|
5
|
+
|
|
6
|
+
env:
|
|
7
|
+
NODE_ENV: "production"
|
|
8
|
+
|
|
9
|
+
# The Dockerfile builds a minimal image with the bundled MCP server
|
|
10
|
+
# and exposes the stdio transport for Smithery's container runtime.
|