premanmcp 0.3.4
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/LICENSE +21 -0
- package/README.md +137 -0
- package/bin/cli.js +168 -0
- package/dist/auth_flow_ui.d.ts +35 -0
- package/dist/auth_flow_ui.js +159 -0
- package/dist/mcp-preview-panel.d.ts +30 -0
- package/dist/mcp-preview-panel.js +166 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1132 -0
- package/dist/user_auth_flow.d.ts +11 -0
- package/dist/user_auth_flow.js +251 -0
- package/package.json +53 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-pane HTML for mcp_preview — shared by the MCP stdio server and
|
|
3
|
+
* scripts/emit-mcp-preview.mjs (browser tab fallback when Cursor does not render mcp-app).
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
export const MCP_PREVIEW_RESOURCE_URI = "ui://preman/mcp-preview";
|
|
9
|
+
export const RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
10
|
+
export function escapeHtmlAttr(s) {
|
|
11
|
+
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
12
|
+
}
|
|
13
|
+
export function escapeHtmlText(s) {
|
|
14
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
15
|
+
}
|
|
16
|
+
export function buildConversionPanelHtml(data) {
|
|
17
|
+
const toStr = (v) => {
|
|
18
|
+
if (v == null)
|
|
19
|
+
return "";
|
|
20
|
+
if (Array.isArray(v))
|
|
21
|
+
return v.filter((x) => x != null).map((x) => String(x)).join("; ");
|
|
22
|
+
if (typeof v === "object") {
|
|
23
|
+
try {
|
|
24
|
+
return Object.entries(v)
|
|
25
|
+
.map(([k, val]) => `${k}: ${val == null ? "" : String(val)}`)
|
|
26
|
+
.join(" · ");
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.stringify(v);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return String(v);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return String(v);
|
|
38
|
+
};
|
|
39
|
+
const intent = toStr(data.intent).trim() || "(unspecified)";
|
|
40
|
+
const method = toStr(data.selection_method).trim() || "unknown";
|
|
41
|
+
const rationale = toStr(data.rationale).trim();
|
|
42
|
+
const upstream = toStr(data.spec_preview?.upstream_base_url).trim();
|
|
43
|
+
const tools = Array.isArray(data.spec_preview?.tools) ? data.spec_preview.tools : [];
|
|
44
|
+
const selectedCount = typeof data.selected_count === "number" ? data.selected_count : tools.length;
|
|
45
|
+
const linkKey = (m, p) => `${(m || "").toUpperCase()} ${p || ""}`.trim();
|
|
46
|
+
const endpointRows = tools
|
|
47
|
+
.map((t) => {
|
|
48
|
+
const ref = t._endpoint_ref ?? {};
|
|
49
|
+
const m = (ref.method ?? "").toUpperCase();
|
|
50
|
+
const p = ref.path_template ?? "";
|
|
51
|
+
const key = linkKey(m, p);
|
|
52
|
+
const tags = Array.isArray(ref.tags) && ref.tags.length > 0 ? ref.tags.join(", ") : "";
|
|
53
|
+
return `
|
|
54
|
+
<div class="row" data-link="${escapeHtmlAttr(key)}">
|
|
55
|
+
<div class="row-head">
|
|
56
|
+
<span class="method ${escapeHtmlAttr(m)}">${escapeHtmlText(m)}</span>
|
|
57
|
+
<span class="path">${escapeHtmlText(p)}</span>
|
|
58
|
+
</div>
|
|
59
|
+
${tags ? `<div class="row-meta">tags: ${escapeHtmlText(tags)}</div>` : ""}
|
|
60
|
+
</div>`;
|
|
61
|
+
})
|
|
62
|
+
.join("");
|
|
63
|
+
const toolRows = tools
|
|
64
|
+
.map((t) => {
|
|
65
|
+
const ref = t._endpoint_ref ?? {};
|
|
66
|
+
const key = linkKey(ref.method, ref.path_template);
|
|
67
|
+
const schemaJson = (() => {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(t.inputSchema ?? {}, null, 2);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return "{}";
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
return `
|
|
76
|
+
<div class="row" data-link="${escapeHtmlAttr(key)}">
|
|
77
|
+
<div class="tool-name">${escapeHtmlText(t.name ?? "(unnamed)")}</div>
|
|
78
|
+
${t.description ? `<div class="tool-desc">${escapeHtmlText(t.description)}</div>` : ""}
|
|
79
|
+
<pre class="tool-schema">${escapeHtmlText(schemaJson)}</pre>
|
|
80
|
+
</div>`;
|
|
81
|
+
})
|
|
82
|
+
.join("");
|
|
83
|
+
const empty = tools.length === 0
|
|
84
|
+
? `<div class="empty">No matching endpoints. Try a different intent or run <code>verify_endpoints_live</code> first.</div>`
|
|
85
|
+
: "";
|
|
86
|
+
return `<!DOCTYPE html>
|
|
87
|
+
<html lang="en">
|
|
88
|
+
<head>
|
|
89
|
+
<meta charset="UTF-8" />
|
|
90
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
91
|
+
<title>PreMan · MCP Preview</title>
|
|
92
|
+
<style>
|
|
93
|
+
:root { color-scheme: light dark; --bg:#0d1117; --bg2:#161b22; --border:#30363d; --fg:#e6edf3; --muted:#8b949e; --accent:#58a6ff; --green:#2ea043; --orange:#bf8700; --red:#cf222e; --purple:#8957e5; }
|
|
94
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
95
|
+
html, body { height:100%; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,sans-serif; font-size:13px; }
|
|
96
|
+
body { display:flex; flex-direction:column; overflow:hidden; }
|
|
97
|
+
header { padding:10px 14px; border-bottom:1px solid var(--border); background:var(--bg2); flex-shrink:0; }
|
|
98
|
+
header h1 { font-size:13px; font-weight:600; color:var(--accent); }
|
|
99
|
+
header .meta { font-size:11px; color:var(--muted); margin-top:3px; }
|
|
100
|
+
header .meta strong { color:var(--fg); font-weight:600; }
|
|
101
|
+
header .rationale { font-size:11px; color:var(--muted); margin-top:5px; font-style:italic; max-width:900px; }
|
|
102
|
+
.columns { flex:1; display:grid; grid-template-columns:1fr 1fr; gap:1px; background:var(--border); overflow:hidden; min-height:0; }
|
|
103
|
+
.col { background:var(--bg); overflow-y:auto; }
|
|
104
|
+
.col-header { padding:7px 12px; font-size:10px; font-weight:700; letter-spacing:0.06em; text-transform:uppercase; color:var(--muted); border-bottom:1px solid var(--border); position:sticky; top:0; background:var(--bg2); z-index:1; }
|
|
105
|
+
.row { padding:9px 12px; border-bottom:1px solid var(--border); transition:background 0.1s; }
|
|
106
|
+
.row:last-child { border-bottom:none; }
|
|
107
|
+
.row:hover, .row.linked { background:rgba(88,166,255,0.08); }
|
|
108
|
+
.row-head { display:flex; align-items:center; gap:8px; }
|
|
109
|
+
.row-meta { font-size:10px; color:var(--muted); margin-top:3px; padding-left:56px; }
|
|
110
|
+
.method { display:inline-block; min-width:48px; text-align:center; padding:2px 6px; border-radius:3px; font-weight:700; font-size:10px; color:white; font-family:SFMono-Regular,Consolas,monospace; }
|
|
111
|
+
.method.GET{background:var(--accent);} .method.POST{background:var(--green);} .method.PATCH{background:var(--orange);} .method.PUT{background:var(--purple);} .method.DELETE{background:var(--red);}
|
|
112
|
+
.path { font-family:SFMono-Regular,Consolas,monospace; font-size:12px; word-break:break-all; }
|
|
113
|
+
.tool-name { font-family:SFMono-Regular,Consolas,monospace; font-size:12px; font-weight:600; color:var(--accent); }
|
|
114
|
+
.tool-desc { font-size:11px; color:var(--muted); margin-top:3px; line-height:1.4; }
|
|
115
|
+
.tool-schema { font-family:SFMono-Regular,Consolas,monospace; font-size:10px; background:var(--bg2); padding:6px 8px; border-radius:3px; margin-top:6px; white-space:pre-wrap; word-break:break-all; max-height:120px; overflow-y:auto; line-height:1.4; color:var(--muted); }
|
|
116
|
+
.empty { padding:24px; text-align:center; color:var(--muted); font-style:italic; }
|
|
117
|
+
.empty code { font-family:SFMono-Regular,Consolas,monospace; color:var(--accent); background:var(--bg2); padding:1px 5px; border-radius:3px; font-style:normal; }
|
|
118
|
+
footer { padding:8px 14px; border-top:1px solid var(--border); background:var(--bg2); display:flex; gap:10px; align-items:center; flex-shrink:0; font-size:11px; color:var(--muted); }
|
|
119
|
+
footer .upstream { font-family:SFMono-Regular,Consolas,monospace; color:var(--accent); }
|
|
120
|
+
footer .deploy-hint { margin-left:auto; }
|
|
121
|
+
footer .deploy-hint code { font-family:SFMono-Regular,Consolas,monospace; color:var(--fg); background:var(--bg); padding:2px 6px; border-radius:3px; }
|
|
122
|
+
</style>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<header>
|
|
126
|
+
<h1>API → MCP Conversion preview</h1>
|
|
127
|
+
<div class="meta">
|
|
128
|
+
Intent: <strong>${escapeHtmlText(intent)}</strong> · Selected <strong>${selectedCount}</strong> endpoint${selectedCount === 1 ? "" : "s"} · Method: ${escapeHtmlText(method)}
|
|
129
|
+
</div>
|
|
130
|
+
${rationale ? `<div class="rationale">${escapeHtmlText(rationale)}</div>` : ""}
|
|
131
|
+
</header>
|
|
132
|
+
<main class="columns">
|
|
133
|
+
<div class="col">
|
|
134
|
+
<div class="col-header">Your API endpoints (${tools.length})</div>
|
|
135
|
+
${endpointRows}${tools.length === 0 ? empty : ""}
|
|
136
|
+
</div>
|
|
137
|
+
<div class="col">
|
|
138
|
+
<div class="col-header">Generated MCP tools (${tools.length})</div>
|
|
139
|
+
${toolRows}${tools.length === 0 ? empty : ""}
|
|
140
|
+
</div>
|
|
141
|
+
</main>
|
|
142
|
+
<footer>
|
|
143
|
+
<span>Upstream: <span class="upstream">${escapeHtmlText(upstream || "(not set)")}</span></span>
|
|
144
|
+
<span class="deploy-hint">Next: ask the agent to call <code>mcp_deploy</code></span>
|
|
145
|
+
</footer>
|
|
146
|
+
<script>
|
|
147
|
+
document.querySelectorAll('.row[data-link]').forEach(row => {
|
|
148
|
+
const key = row.getAttribute('data-link');
|
|
149
|
+
if (!key) return;
|
|
150
|
+
const matches = () => document.querySelectorAll('[data-link="' + CSS.escape(key) + '"]');
|
|
151
|
+
row.addEventListener('mouseenter', () => matches().forEach(r => r.classList.add('linked')));
|
|
152
|
+
row.addEventListener('mouseleave', () => matches().forEach(r => r.classList.remove('linked')));
|
|
153
|
+
});
|
|
154
|
+
</script>
|
|
155
|
+
</body>
|
|
156
|
+
</html>`;
|
|
157
|
+
}
|
|
158
|
+
export async function writeMcpPreviewFile(panelHtml) {
|
|
159
|
+
const outPath = path.join(process.cwd(), "preman-mcp", "mcp-preview-last.html");
|
|
160
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
161
|
+
await fs.writeFile(outPath, panelHtml, "utf8");
|
|
162
|
+
return {
|
|
163
|
+
absolutePath: outPath,
|
|
164
|
+
fileUrl: pathToFileURL(outPath).href,
|
|
165
|
+
};
|
|
166
|
+
}
|
package/dist/server.d.ts
ADDED