@showly/mcp-server 0.1.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/README.md +81 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +231 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +34 -0
- package/dist/manifest-types.d.ts +34 -0
- package/dist/manifest-types.js +6 -0
- package/manifest.json +87 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @showly/mcp-server
|
|
2
|
+
|
|
3
|
+
Connect Claude Code or Codex to the [Showly](https://showly.ai) MCP server, so
|
|
4
|
+
you can preview and deploy a site directly from your terminal AI.
|
|
5
|
+
|
|
6
|
+
## Quickstart
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx @showly/mcp-server install --to claude-code # or --to codex
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The installer writes the Showly MCP server block to `~/.claude.json`
|
|
13
|
+
(or `~/.codex/config.toml`). No token to paste: the next time you call a Showly
|
|
14
|
+
tool, your agent discovers Showly's OAuth authorization server from the
|
|
15
|
+
endpoint and opens a browser tab for you to sign in and approve the scopes
|
|
16
|
+
(standard MCP authorization).
|
|
17
|
+
|
|
18
|
+
## What you get
|
|
19
|
+
|
|
20
|
+
Read-only tools available to any token:
|
|
21
|
+
|
|
22
|
+
- `list_projects`, `list_sites`, `get_site_context`, `list_templates`
|
|
23
|
+
- `get_preview_status`, `get_deployment_logs`, `diagnose_deployment`
|
|
24
|
+
|
|
25
|
+
Write tools that stage and materialise previews:
|
|
26
|
+
|
|
27
|
+
- `create_change_plan` — turn a natural-language request into a plan
|
|
28
|
+
- `apply_site_patch` — stage one or more file edits as a _changeset_
|
|
29
|
+
- `create_preview` — build the changeset and return a real preview URL
|
|
30
|
+
- `retry_deployment` — re-run a failed preview build
|
|
31
|
+
- `run_checks` — read the lint / typecheck / build status of a preview
|
|
32
|
+
- `create_site_from_template` — bootstrap a new site from a template
|
|
33
|
+
- `request_publish` — open a publish approval (completed in the web UI)
|
|
34
|
+
|
|
35
|
+
Production paths (`publish_site`, `rollback_deployment`) are deliberately
|
|
36
|
+
not exposed via MCP at all — they don't appear in `tools/list`. The agent's
|
|
37
|
+
path to production is `request_publish`, whose success response includes a
|
|
38
|
+
`webApprovalUrl` deep link; the user opens that link to complete approval
|
|
39
|
+
and step-up MFA in the Showly web UI.
|
|
40
|
+
|
|
41
|
+
## Environment
|
|
42
|
+
|
|
43
|
+
| Variable | Default | What it controls |
|
|
44
|
+
| ---------------- | ----------------------- | ---------------------- |
|
|
45
|
+
| `SHOWLY_MCP_URL` | `https://mcp.showly.ai` | MCP transport endpoint |
|
|
46
|
+
| `SHOWLY_API_URL` | `https://api.showly.ai` | OAuth + API base |
|
|
47
|
+
|
|
48
|
+
## Without an installer
|
|
49
|
+
|
|
50
|
+
If your agent isn't supported by the installer, run:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx @showly/mcp-server install --to stdout
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
and paste the snippet into the agent's configuration manually. The manifest
|
|
57
|
+
that drives both targets is also accessible programmatically:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { manifest } from "@showly/mcp-server";
|
|
61
|
+
console.log(manifest.tools.map((t) => t.name));
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Troubleshooting
|
|
65
|
+
|
|
66
|
+
**Codex doesn't list the showly tools.** Older Codex CLI versions only reach
|
|
67
|
+
remote (streamable HTTP) MCP servers when the rmcp client is enabled. Either
|
|
68
|
+
update Codex, or add to `~/.codex/config.toml`:
|
|
69
|
+
|
|
70
|
+
```toml
|
|
71
|
+
[features]
|
|
72
|
+
experimental_use_rmcp_client = true
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
(Current Codex versions need no flag — a bare `url` under
|
|
76
|
+
`[mcp_servers.showly]` is the whole config.)
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
Proprietary — © Showly. This package is distributed for use with the Showly
|
|
81
|
+
hosting service; it is not open source. See https://showly.ai/terms.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type Target = "claude-code" | "codex" | "stdout";
|
|
3
|
+
export declare function buildClaudeCodeSnippet(opts: {
|
|
4
|
+
url: string;
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
}): Record<string, unknown>;
|
|
7
|
+
export declare function buildCodexSnippet(opts: {
|
|
8
|
+
url: string;
|
|
9
|
+
apiUrl?: string;
|
|
10
|
+
}): string;
|
|
11
|
+
export declare function resolveUrls(env?: NodeJS.ProcessEnv): {
|
|
12
|
+
url: string;
|
|
13
|
+
apiUrl: string;
|
|
14
|
+
};
|
|
15
|
+
export type InstallResult = {
|
|
16
|
+
target: Target;
|
|
17
|
+
path: string | null;
|
|
18
|
+
snippet: string;
|
|
19
|
+
wrote: boolean;
|
|
20
|
+
alreadyConfigured: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function performInstall(target: Target, env?: NodeJS.ProcessEnv): InstallResult;
|
|
23
|
+
/**
|
|
24
|
+
* True when this module is the program entrypoint.
|
|
25
|
+
*
|
|
26
|
+
* Must handle the `npm bin` case: an installed package exposes the CLI as
|
|
27
|
+
* `node_modules/.bin/showly-mcp`, a symlink to `dist/cli.js`. Node sets
|
|
28
|
+
* `process.argv[1]` to the symlink path (`…/.bin/showly-mcp`), while
|
|
29
|
+
* `import.meta.url` is the real `…/dist/cli.js`. The old guard compared the
|
|
30
|
+
* two strings (and looked for a `cli.js` suffix on argv[1]) so it silently
|
|
31
|
+
* failed through the symlink — `showly-mcp <cmd>` exited 0 with no output.
|
|
32
|
+
*
|
|
33
|
+
* Resolve argv[1] to its real path before comparing, so the symlink and the
|
|
34
|
+
* target reconcile. Exported for unit testing (the bin path can't be
|
|
35
|
+
* exercised from an in-process test runner otherwise).
|
|
36
|
+
*/
|
|
37
|
+
export declare function isMainModule(argv1: string | undefined, metaUrl: string): boolean;
|
|
38
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// packages/showly-mcp-server/src/cli.ts — install helper.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// showly-mcp install --to claude-code # writes ~/.claude.json
|
|
6
|
+
// showly-mcp install --to codex # writes ~/.codex/config.toml
|
|
7
|
+
// showly-mcp install --to stdout # prints the snippet for manual paste
|
|
8
|
+
// showly-mcp manifest # prints manifest.json
|
|
9
|
+
// showly-mcp --help
|
|
10
|
+
//
|
|
11
|
+
// The CLI only writes the MCP server config block — it doesn't touch network.
|
|
12
|
+
// The agent discovers OAuth from the server and runs the browser sign-in on
|
|
13
|
+
// first use (standard MCP authorization).
|
|
14
|
+
import { readFileSync, mkdirSync, writeFileSync, existsSync, realpathSync, } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { pathToFileURL } from "node:url";
|
|
18
|
+
import { loadManifest } from "./index.js";
|
|
19
|
+
function usage() {
|
|
20
|
+
return [
|
|
21
|
+
"Showly MCP server installer",
|
|
22
|
+
"",
|
|
23
|
+
"Usage:",
|
|
24
|
+
" showly-mcp install --to <claude-code|codex|stdout>",
|
|
25
|
+
" showly-mcp manifest",
|
|
26
|
+
"",
|
|
27
|
+
"Environment overrides:",
|
|
28
|
+
" SHOWLY_MCP_URL full URL to your MCP endpoint (default https://mcp.showly.ai)",
|
|
29
|
+
" SHOWLY_API_URL full URL to your API (default https://api.showly.ai)",
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
32
|
+
// The MCP server config is intentionally MINIMAL: just the transport + URL.
|
|
33
|
+
// OAuth is NOT configured inline — a standards-compliant MCP client (Claude
|
|
34
|
+
// Code / Codex) discovers it from the server: it connects, gets 401 +
|
|
35
|
+
// WWW-Authenticate, fetches the server's protected-resource metadata, then the
|
|
36
|
+
// authorization-server metadata, dynamically registers, and runs
|
|
37
|
+
// authorization_code + PKCE in the browser. There is no client-readable
|
|
38
|
+
// `oauth` field in the host config schema, so emitting one (as a prior version
|
|
39
|
+
// did) was a no-op that misled rather than helped. `apiUrl` is retained in the
|
|
40
|
+
// signature for the stdout help text + the device-flow fallback docs.
|
|
41
|
+
export function buildClaudeCodeSnippet(opts) {
|
|
42
|
+
return {
|
|
43
|
+
mcpServers: {
|
|
44
|
+
showly: {
|
|
45
|
+
type: "http",
|
|
46
|
+
url: opts.url,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Codex's HTTP MCP config schema has no `type` key — a bare `url` is what
|
|
52
|
+
// selects the streamable-HTTP transport (stdio servers use `command` instead).
|
|
53
|
+
// Codex's TOML deserialization rejects unknown keys, so emitting one risks
|
|
54
|
+
// breaking the whole config file, not just our entry. Codex versions that
|
|
55
|
+
// predate the rmcp client need `[features] experimental_use_rmcp_client =
|
|
56
|
+
// true` on top — documented in the README rather than written here, so we
|
|
57
|
+
// never inject a duplicate [features] table into a user's config.
|
|
58
|
+
export function buildCodexSnippet(opts) {
|
|
59
|
+
return [
|
|
60
|
+
"# Added by @showly/mcp-server install",
|
|
61
|
+
"",
|
|
62
|
+
"[mcp_servers.showly]",
|
|
63
|
+
`url = "${opts.url}"`,
|
|
64
|
+
"",
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
export function resolveUrls(env = process.env) {
|
|
68
|
+
const manifest = loadManifest();
|
|
69
|
+
return {
|
|
70
|
+
url: env[manifest.mcp.endpoints.url_env] ?? manifest.mcp.endpoints.default_url,
|
|
71
|
+
apiUrl: env[manifest.mcp.endpoints.api_url_env] ?? "https://api.showly.ai",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function performInstall(target, env = process.env) {
|
|
75
|
+
const { url, apiUrl } = resolveUrls(env);
|
|
76
|
+
if (target === "stdout") {
|
|
77
|
+
const obj = buildClaudeCodeSnippet({ url, apiUrl });
|
|
78
|
+
return {
|
|
79
|
+
target,
|
|
80
|
+
path: null,
|
|
81
|
+
snippet: "# claude-code (~/.claude.json):\n" +
|
|
82
|
+
JSON.stringify(obj, null, 2) +
|
|
83
|
+
"\n\n# codex (~/.codex/config.toml):\n" +
|
|
84
|
+
buildCodexSnippet({ url, apiUrl }),
|
|
85
|
+
wrote: false,
|
|
86
|
+
alreadyConfigured: false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (target === "claude-code") {
|
|
90
|
+
// User-scope MCP servers live in ~/.claude.json (top-level `mcpServers`),
|
|
91
|
+
// NOT ~/.claude/settings.json — Claude Code never reads mcpServers from
|
|
92
|
+
// settings.json, so a prior version of this installer wrote a block that
|
|
93
|
+
// was silently ignored.
|
|
94
|
+
const path = join(homedir(), ".claude.json");
|
|
95
|
+
const existing = existsSync(path)
|
|
96
|
+
? safeReadJson(path)
|
|
97
|
+
: { mcpServers: {} };
|
|
98
|
+
const before = JSON.stringify(existing);
|
|
99
|
+
const snippet = buildClaudeCodeSnippet({ url, apiUrl });
|
|
100
|
+
const merged = {
|
|
101
|
+
...existing,
|
|
102
|
+
mcpServers: {
|
|
103
|
+
...(typeof existing.mcpServers === "object" && existing.mcpServers
|
|
104
|
+
? existing.mcpServers
|
|
105
|
+
: {}),
|
|
106
|
+
showly: snippet.mcpServers.showly,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const after = JSON.stringify(merged);
|
|
110
|
+
if (before === after) {
|
|
111
|
+
return {
|
|
112
|
+
target,
|
|
113
|
+
path,
|
|
114
|
+
snippet: JSON.stringify(merged, null, 2),
|
|
115
|
+
wrote: false,
|
|
116
|
+
alreadyConfigured: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
120
|
+
writeFileSync(path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
121
|
+
return {
|
|
122
|
+
target,
|
|
123
|
+
path,
|
|
124
|
+
snippet: JSON.stringify(merged, null, 2),
|
|
125
|
+
wrote: true,
|
|
126
|
+
alreadyConfigured: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// codex
|
|
130
|
+
const path = join(homedir(), ".codex", "config.toml");
|
|
131
|
+
const snippet = buildCodexSnippet({ url, apiUrl });
|
|
132
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
133
|
+
if (existing.includes("[mcp_servers.showly]")) {
|
|
134
|
+
return {
|
|
135
|
+
target,
|
|
136
|
+
path,
|
|
137
|
+
snippet,
|
|
138
|
+
wrote: false,
|
|
139
|
+
alreadyConfigured: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
143
|
+
writeFileSync(path, existing.length > 0 && !existing.endsWith("\n")
|
|
144
|
+
? `${existing}\n${snippet}`
|
|
145
|
+
: `${existing}${snippet}`, "utf8");
|
|
146
|
+
return {
|
|
147
|
+
target,
|
|
148
|
+
path,
|
|
149
|
+
snippet,
|
|
150
|
+
wrote: true,
|
|
151
|
+
alreadyConfigured: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function safeReadJson(path) {
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function main(argv) {
|
|
163
|
+
const [cmd, ...rest] = argv.slice(2);
|
|
164
|
+
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
165
|
+
console.log(usage());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (cmd === "manifest") {
|
|
169
|
+
console.log(JSON.stringify(loadManifest(), null, 2));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (cmd === "install") {
|
|
173
|
+
const toIdx = rest.indexOf("--to");
|
|
174
|
+
if (toIdx === -1 || !rest[toIdx + 1]) {
|
|
175
|
+
console.error("install: --to <target> is required");
|
|
176
|
+
process.exitCode = 2;
|
|
177
|
+
console.error(usage());
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const target = rest[toIdx + 1];
|
|
181
|
+
if (!["claude-code", "codex", "stdout"].includes(target)) {
|
|
182
|
+
console.error(`install: unknown target "${target}"`);
|
|
183
|
+
process.exitCode = 2;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const result = performInstall(target);
|
|
187
|
+
if (target === "stdout") {
|
|
188
|
+
console.log(result.snippet);
|
|
189
|
+
}
|
|
190
|
+
else if (result.alreadyConfigured) {
|
|
191
|
+
console.log(`Already configured at ${result.path}`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.log(`Wrote ${result.path}`);
|
|
195
|
+
console.log("");
|
|
196
|
+
console.log("Next: open Claude Code / Codex and run any read tool (e.g. list_sites).");
|
|
197
|
+
console.log("The agent will pop a browser tab for you to authorize the connection.");
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
console.error(`unknown command: ${cmd}`);
|
|
202
|
+
process.exitCode = 2;
|
|
203
|
+
console.error(usage());
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* True when this module is the program entrypoint.
|
|
207
|
+
*
|
|
208
|
+
* Must handle the `npm bin` case: an installed package exposes the CLI as
|
|
209
|
+
* `node_modules/.bin/showly-mcp`, a symlink to `dist/cli.js`. Node sets
|
|
210
|
+
* `process.argv[1]` to the symlink path (`…/.bin/showly-mcp`), while
|
|
211
|
+
* `import.meta.url` is the real `…/dist/cli.js`. The old guard compared the
|
|
212
|
+
* two strings (and looked for a `cli.js` suffix on argv[1]) so it silently
|
|
213
|
+
* failed through the symlink — `showly-mcp <cmd>` exited 0 with no output.
|
|
214
|
+
*
|
|
215
|
+
* Resolve argv[1] to its real path before comparing, so the symlink and the
|
|
216
|
+
* target reconcile. Exported for unit testing (the bin path can't be
|
|
217
|
+
* exercised from an in-process test runner otherwise).
|
|
218
|
+
*/
|
|
219
|
+
export function isMainModule(argv1, metaUrl) {
|
|
220
|
+
if (!argv1)
|
|
221
|
+
return false;
|
|
222
|
+
try {
|
|
223
|
+
return pathToFileURL(realpathSync(argv1)).href === metaUrl;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (isMainModule(process.argv[1], import.meta.url)) {
|
|
230
|
+
main(process.argv);
|
|
231
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SkillManifest, ToolManifestEntry } from "./manifest-types.js";
|
|
2
|
+
export type { SkillManifest, ToolManifestEntry, ToolKind, } from "./manifest-types.js";
|
|
3
|
+
export declare function loadManifest(): SkillManifest;
|
|
4
|
+
/** Sync read of the manifest; throws if file missing or malformed. */
|
|
5
|
+
export declare const manifest: SkillManifest;
|
|
6
|
+
/** Tool names safe to call directly from an MCP-origin token. */
|
|
7
|
+
export declare const INVOKABLE_TOOLS: readonly string[];
|
|
8
|
+
/** Tool names that are intentionally blocked at the wrapper layer. */
|
|
9
|
+
export declare const MCP_BLOCKED_TOOLS: readonly string[];
|
|
10
|
+
export declare function getToolEntry(name: string): ToolManifestEntry | undefined;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// packages/showly-mcp-server/src/index.ts — programmatic API.
|
|
2
|
+
//
|
|
3
|
+
// Consumers (mostly tests + integration code) can do:
|
|
4
|
+
//
|
|
5
|
+
// import { loadManifest, manifest, INVOKABLE_TOOLS } from "@showly/mcp-server";
|
|
6
|
+
//
|
|
7
|
+
// The Claude Code skill loader and the Codex plugin loader both
|
|
8
|
+
// resolve manifest.json directly from their package.json fields; the
|
|
9
|
+
// JS surface here is for programmatic clients (tests, integrators)
|
|
10
|
+
// that want a typed view.
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { dirname, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
// We ship manifest.json at the package root, so from dist/index.js
|
|
16
|
+
// it lives one level up.
|
|
17
|
+
const MANIFEST_PATH = resolve(HERE, "..", "manifest.json");
|
|
18
|
+
let cached = null;
|
|
19
|
+
export function loadManifest() {
|
|
20
|
+
if (cached)
|
|
21
|
+
return cached;
|
|
22
|
+
const raw = readFileSync(MANIFEST_PATH, "utf8");
|
|
23
|
+
cached = JSON.parse(raw);
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
/** Sync read of the manifest; throws if file missing or malformed. */
|
|
27
|
+
export const manifest = loadManifest();
|
|
28
|
+
/** Tool names safe to call directly from an MCP-origin token. */
|
|
29
|
+
export const INVOKABLE_TOOLS = Object.freeze(manifest.tools.filter((t) => !t.mcp_origin_blocked).map((t) => t.name));
|
|
30
|
+
/** Tool names that are intentionally blocked at the wrapper layer. */
|
|
31
|
+
export const MCP_BLOCKED_TOOLS = Object.freeze(manifest.tools.filter((t) => t.mcp_origin_blocked).map((t) => t.name));
|
|
32
|
+
export function getToolEntry(name) {
|
|
33
|
+
return manifest.tools.find((t) => t.name === name);
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ToolKind = "read" | "write" | "production";
|
|
2
|
+
export type ToolManifestEntry = {
|
|
3
|
+
name: string;
|
|
4
|
+
kind: ToolKind;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
mcp_origin_blocked?: boolean;
|
|
7
|
+
note?: string;
|
|
8
|
+
};
|
|
9
|
+
export type SkillManifest = {
|
|
10
|
+
name: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
version: string;
|
|
13
|
+
description: string;
|
|
14
|
+
homepage: string;
|
|
15
|
+
publisher: string;
|
|
16
|
+
mcp: {
|
|
17
|
+
transport: "streamable-http" | "stdio";
|
|
18
|
+
auth: {
|
|
19
|
+
kind: "oauth" | "oauth-device-flow" | "api-token";
|
|
20
|
+
/** Scopes the client requests by default at consent time. */
|
|
21
|
+
default_scopes: string[];
|
|
22
|
+
};
|
|
23
|
+
endpoints: {
|
|
24
|
+
/** Env var that overrides the MCP server URL. */
|
|
25
|
+
url_env: string;
|
|
26
|
+
/** Default MCP server URL (the resource a client connects to). */
|
|
27
|
+
default_url: string;
|
|
28
|
+
/** Env var that overrides the API base (device-flow fallback + docs). */
|
|
29
|
+
api_url_env: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
tools: ToolManifestEntry[];
|
|
33
|
+
quickstart: string[];
|
|
34
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// packages/showly-mcp-server/src/manifest-types.ts — shape of manifest.json.
|
|
2
|
+
//
|
|
3
|
+
// The manifest is the contract between the package and the host agent
|
|
4
|
+
// (Claude Code, Codex, future plugins). Bumping a field requires a
|
|
5
|
+
// minor version bump and a SDK release.
|
|
6
|
+
export {};
|
package/manifest.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://showly.ai/schemas/skill-manifest-v1.json",
|
|
3
|
+
"name": "showly",
|
|
4
|
+
"displayName": "Showly",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Deploy and manage Showly sites from inside Claude Code / Codex.",
|
|
7
|
+
"homepage": "https://showly.ai/docs/skills",
|
|
8
|
+
"publisher": "Showly",
|
|
9
|
+
"mcp": {
|
|
10
|
+
"transport": "streamable-http",
|
|
11
|
+
"auth": {
|
|
12
|
+
"kind": "oauth",
|
|
13
|
+
"default_scopes": [
|
|
14
|
+
"project:read",
|
|
15
|
+
"site:read",
|
|
16
|
+
"site:write",
|
|
17
|
+
"preview:read",
|
|
18
|
+
"preview:create",
|
|
19
|
+
"checks:run",
|
|
20
|
+
"publish:request",
|
|
21
|
+
"logs:read",
|
|
22
|
+
"template:read",
|
|
23
|
+
"template:create"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"endpoints": {
|
|
27
|
+
"url_env": "SHOWLY_MCP_URL",
|
|
28
|
+
"default_url": "https://mcp.showly.ai",
|
|
29
|
+
"api_url_env": "SHOWLY_API_URL"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"tools": [
|
|
33
|
+
{ "name": "list_projects", "kind": "read", "scopes": ["project:read"] },
|
|
34
|
+
{ "name": "list_sites", "kind": "read", "scopes": ["site:read"] },
|
|
35
|
+
{ "name": "get_site_context", "kind": "read", "scopes": ["site:read"] },
|
|
36
|
+
{
|
|
37
|
+
"name": "get_preview_status",
|
|
38
|
+
"kind": "read",
|
|
39
|
+
"scopes": ["preview:read"]
|
|
40
|
+
},
|
|
41
|
+
{ "name": "get_deployment_logs", "kind": "read", "scopes": ["logs:read"] },
|
|
42
|
+
{
|
|
43
|
+
"name": "diagnose_deployment",
|
|
44
|
+
"kind": "read",
|
|
45
|
+
"scopes": ["logs:read"]
|
|
46
|
+
},
|
|
47
|
+
{ "name": "list_templates", "kind": "read", "scopes": ["template:read"] },
|
|
48
|
+
{ "name": "create_change_plan", "kind": "write", "scopes": ["site:read"] },
|
|
49
|
+
{ "name": "apply_site_patch", "kind": "write", "scopes": ["site:write"] },
|
|
50
|
+
{ "name": "create_preview", "kind": "write", "scopes": ["preview:create"] },
|
|
51
|
+
{
|
|
52
|
+
"name": "retry_deployment",
|
|
53
|
+
"kind": "write",
|
|
54
|
+
"scopes": ["preview:create"]
|
|
55
|
+
},
|
|
56
|
+
{ "name": "run_checks", "kind": "write", "scopes": ["checks:run"] },
|
|
57
|
+
{
|
|
58
|
+
"name": "request_publish",
|
|
59
|
+
"kind": "write",
|
|
60
|
+
"scopes": ["publish:request"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "create_site_from_template",
|
|
64
|
+
"kind": "write",
|
|
65
|
+
"scopes": ["template:create", "site:write"]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "publish_site",
|
|
69
|
+
"kind": "production",
|
|
70
|
+
"scopes": ["production:deploy"],
|
|
71
|
+
"mcp_origin_blocked": true,
|
|
72
|
+
"note": "Production-side deploy. MCP-origin calls are rejected; trigger from the web UI with step-up MFA + approval."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "rollback_deployment",
|
|
76
|
+
"kind": "production",
|
|
77
|
+
"scopes": ["production:rollback"],
|
|
78
|
+
"mcp_origin_blocked": true,
|
|
79
|
+
"note": "Production-side rollback. Same constraint as publish_site."
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"quickstart": [
|
|
83
|
+
"Install: npx @showly/mcp-server install --to claude-code",
|
|
84
|
+
"Authorize: run any read tool — your agent opens the consent URL in your browser.",
|
|
85
|
+
"Demo: ask the agent to 'preview a one-line H1 change' on a site you own."
|
|
86
|
+
]
|
|
87
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@showly/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Connect Claude Code / Codex to the Showly MCP server — preview and deploy sites from your agent.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"showly-mcp": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./manifest": "./manifest.json",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"manifest.json",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"build:publish": "tsc -p tsconfig.json --declarationMap false",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"test": "tsx --test \"src/**/*.test.ts\" \"src/*.test.ts\"",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"prepublishOnly": "rm -rf dist && pnpm run build:publish"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"claude-code",
|
|
41
|
+
"codex",
|
|
42
|
+
"mcp",
|
|
43
|
+
"mcp-server",
|
|
44
|
+
"showly",
|
|
45
|
+
"hosting",
|
|
46
|
+
"agent"
|
|
47
|
+
],
|
|
48
|
+
"homepage": "https://showly.ai/docs/mcp",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://showly.ai/support"
|
|
51
|
+
},
|
|
52
|
+
"claude-code-skill": {
|
|
53
|
+
"name": "showly",
|
|
54
|
+
"version": "0.1.0",
|
|
55
|
+
"description": "Deploy and manage Showly sites from inside Claude Code.",
|
|
56
|
+
"mcp-server": {
|
|
57
|
+
"url-env": "SHOWLY_MCP_URL",
|
|
58
|
+
"default-url": "https://mcp.showly.ai",
|
|
59
|
+
"auth": "oauth",
|
|
60
|
+
"api-url-env": "SHOWLY_API_URL"
|
|
61
|
+
},
|
|
62
|
+
"tools-prefix": "showly",
|
|
63
|
+
"manifest": "manifest.json"
|
|
64
|
+
},
|
|
65
|
+
"codex-plugin": {
|
|
66
|
+
"name": "showly",
|
|
67
|
+
"version": "0.1.0",
|
|
68
|
+
"type": "mcp-server",
|
|
69
|
+
"manifest": "manifest.json"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@showly/eslint-config": "workspace:^",
|
|
73
|
+
"@types/node": "^25.9.1",
|
|
74
|
+
"eslint": "^9.18.0",
|
|
75
|
+
"tsx": "^4.22.4",
|
|
76
|
+
"typescript": "^6.0.3"
|
|
77
|
+
}
|
|
78
|
+
}
|