packkit-mcp 0.1.3 → 0.2.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/README.md +6 -0
- package/package.json +2 -2
- package/server.js +63 -25
package/README.md
CHANGED
|
@@ -25,6 +25,12 @@ Add to your MCP client config (e.g. Claude Desktop's `claude_desktop_config.json
|
|
|
25
25
|
|
|
26
26
|
Then ask your agent things like *"scaffold a React component library called ui with Storybook"* or *"preview a Hono service named api"*.
|
|
27
27
|
|
|
28
|
+
Also listed on the [official MCP registry](https://registry.modelcontextprotocol.io) as `io.github.DanMat/packkit-mcp` and on [Glama](https://glama.ai/mcp/servers/DanMat/create-packkit).
|
|
29
|
+
|
|
30
|
+
## Releasing
|
|
31
|
+
|
|
32
|
+
Publishing a new version touches npm, the official MCP registry and Glama — see **[RELEASING.md](RELEASING.md)**.
|
|
33
|
+
|
|
28
34
|
## License
|
|
29
35
|
|
|
30
36
|
[MIT](https://github.com/DanMat/create-packkit/blob/main/LICENSE) © DanMat
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "packkit-mcp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.DanMat/packkit-mcp",
|
|
5
5
|
"description": "MCP server for Packkit — let AI agents scaffold modern npm packages, CLIs, services, and apps as a native tool.",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"homepage": "https://danmat.github.io/create-packkit/",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
|
-
"create-packkit": "^2.
|
|
37
|
+
"create-packkit": "^2.8.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/server.js
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
// Packkit MCP server — exposes Packkit scaffolding as Model Context Protocol
|
|
3
3
|
// tools so agents (Claude Desktop, Cursor, etc.) can generate projects natively.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
writeProject,
|
|
8
|
+
existingEntries,
|
|
9
|
+
gitInit,
|
|
10
|
+
installDeps,
|
|
11
|
+
hasCommand,
|
|
12
|
+
githubLogin,
|
|
13
|
+
createGithubRepo,
|
|
14
|
+
writeLockfile,
|
|
15
|
+
commitAll,
|
|
16
|
+
} from 'create-packkit/scaffold';
|
|
9
17
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
10
18
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
19
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -27,13 +35,18 @@ const TOOLS = [
|
|
|
27
35
|
{
|
|
28
36
|
name: 'packkit_schema',
|
|
29
37
|
description:
|
|
30
|
-
'
|
|
38
|
+
'START HERE. Returns every Packkit option, preset, and shortcut alias as JSON. ' +
|
|
39
|
+
'Call this before scaffolding so you pick a preset that matches what the user actually wants — ' +
|
|
40
|
+
'presets range from single libraries to CLIs, HTTP services, SPAs, and full-stack monorepos, ' +
|
|
41
|
+
'and guessing without reading them is the main cause of scaffolding the wrong shape.',
|
|
31
42
|
inputSchema: { type: 'object', properties: {} },
|
|
32
43
|
},
|
|
33
44
|
{
|
|
34
45
|
name: 'packkit_preview',
|
|
35
46
|
description:
|
|
36
|
-
'
|
|
47
|
+
'Show the project structure a config would produce — the full file tree and stack summary — without writing anything. ' +
|
|
48
|
+
'Use this to check the layout before committing to it, and to show the user what they are about to get. ' +
|
|
49
|
+
'Cheap and side-effect free, so prefer it over scaffolding to a throwaway directory to see what happens.',
|
|
37
50
|
inputSchema: {
|
|
38
51
|
type: 'object',
|
|
39
52
|
properties: {
|
|
@@ -47,7 +60,10 @@ const TOOLS = [
|
|
|
47
60
|
{
|
|
48
61
|
name: 'packkit_scaffold',
|
|
49
62
|
description:
|
|
50
|
-
'Generate a project to disk. Writes files under <directory>/<name> (or ./<name>), optionally runs git init
|
|
63
|
+
'Generate a project to disk. Writes files under <directory>/<name> (or ./<name>), optionally runs git init, ' +
|
|
64
|
+
'installs dependencies, and creates the GitHub repository. ' +
|
|
65
|
+
'Call packkit_schema first if you have not already — picking the preset by guess is how projects end up the wrong shape. ' +
|
|
66
|
+
'If the target directory already has files in it (an existing clone, for instance), pass merge: true rather than failing.',
|
|
51
67
|
inputSchema: {
|
|
52
68
|
type: 'object',
|
|
53
69
|
properties: {
|
|
@@ -57,6 +73,9 @@ const TOOLS = [
|
|
|
57
73
|
directory: { type: 'string', description: 'Parent directory to create the project in (default: current working directory)' },
|
|
58
74
|
install: { type: 'boolean', description: 'Install dependencies (default false)' },
|
|
59
75
|
git: { type: 'boolean', description: 'git init + initial commit (default false)' },
|
|
76
|
+
merge: { type: 'boolean', description: 'Scaffold into a non-empty directory. Existing files are never overwritten — colliding ones are skipped and reported. Use this when the target is an already-cloned repo.' },
|
|
77
|
+
github: { type: 'boolean', description: 'Create the repository on GitHub and push to it, using the `gh` CLI. Requires git. Private unless "public" is set.' },
|
|
78
|
+
public: { type: 'boolean', description: 'When creating the repository, make it public (default: private)' },
|
|
60
79
|
},
|
|
61
80
|
required: ['name'],
|
|
62
81
|
},
|
|
@@ -66,19 +85,11 @@ const TOOLS = [
|
|
|
66
85
|
const text = (t) => ({ content: [{ type: 'text', text: t }] });
|
|
67
86
|
const fail = (t) => ({ content: [{ type: 'text', text: t }], isError: true });
|
|
68
87
|
|
|
69
|
-
async function writeProject(targetDir, files) {
|
|
70
|
-
for (const [rel, contents] of Object.entries(files)) {
|
|
71
|
-
const full = join(targetDir, rel);
|
|
72
|
-
await mkdir(dirname(full), { recursive: true });
|
|
73
|
-
await writeFile(full, contents);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
88
|
function fileTree(files) {
|
|
78
89
|
return Object.keys(files).sort().map((p) => ` ${p}`).join('\n');
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
const server = new Server({ name: 'packkit', version: '0.1
|
|
92
|
+
const server = new Server({ name: 'packkit', version: '0.2.1' }, { capabilities: { tools: {} } });
|
|
82
93
|
|
|
83
94
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
84
95
|
|
|
@@ -99,25 +110,52 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
99
110
|
if (!config.name) return fail('A "name" is required.');
|
|
100
111
|
const parent = args.directory ? resolve(args.directory) : process.cwd();
|
|
101
112
|
const targetDir = join(parent, config.name);
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
const occupied = existingEntries(targetDir);
|
|
114
|
+
if (occupied.length && !args.merge) {
|
|
115
|
+
return fail(
|
|
116
|
+
`Target directory "${targetDir}" is not empty (${occupied.slice(0, 4).join(', ')}). ` +
|
|
117
|
+
'Pass merge: true to scaffold around the existing files — they are never overwritten.',
|
|
118
|
+
);
|
|
104
119
|
}
|
|
120
|
+
|
|
121
|
+
// Creating the repo has to be settled before generating: the repository
|
|
122
|
+
// URL is baked into package.json links and README badges.
|
|
123
|
+
let slug = null;
|
|
124
|
+
if (args.github) {
|
|
125
|
+
if (!args.git) return fail('github: true also needs git: true — there must be a commit to push.');
|
|
126
|
+
if (!hasCommand('gh')) return fail('github: true needs the GitHub CLI (https://cli.github.com).');
|
|
127
|
+
const login = githubLogin();
|
|
128
|
+
if (!login) return fail('github: true needs an authenticated GitHub CLI. Run: gh auth login');
|
|
129
|
+
slug = `${login}/${config.name}`;
|
|
130
|
+
config.repo = `https://github.com/${slug}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
const { files, summary } = generate(config);
|
|
106
|
-
await writeProject(targetDir, files);
|
|
134
|
+
const { written, skipped } = await writeProject(targetDir, files, { merge: !!args.merge });
|
|
107
135
|
|
|
108
136
|
const steps = [];
|
|
137
|
+
if (skipped.length) steps.push(`kept ${skipped.length} existing file(s): ${skipped.join(', ')}`);
|
|
109
138
|
if (args.git) {
|
|
110
|
-
|
|
111
|
-
spawnSync('git', ['add', '-A'], { cwd: targetDir });
|
|
112
|
-
spawnSync('git', ['commit', '-m', 'Initial commit from Packkit', '--quiet'], { cwd: targetDir });
|
|
139
|
+
gitInit(targetDir);
|
|
113
140
|
steps.push('git initialized');
|
|
114
141
|
}
|
|
115
142
|
if (args.install) {
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
steps.push(installDeps(config.packageManager, targetDir) ? 'dependencies installed' : 'install failed (run it manually)');
|
|
144
|
+
}
|
|
145
|
+
if (slug) {
|
|
146
|
+
// Pushing without a lockfile makes the new repo's first CI run fail.
|
|
147
|
+
if (!args.install) writeLockfile(config.packageManager, targetDir);
|
|
148
|
+
commitAll(targetDir, 'Add lockfile');
|
|
149
|
+
const res = createGithubRepo({
|
|
150
|
+
slug,
|
|
151
|
+
description: config.description,
|
|
152
|
+
private: !args.public,
|
|
153
|
+
cwd: targetDir,
|
|
154
|
+
});
|
|
155
|
+
steps.push(res.ok ? `pushed to ${config.repo}` : `repo creation failed: ${res.error}`);
|
|
118
156
|
}
|
|
119
157
|
return text(
|
|
120
|
-
`Created ${summary.name} at ${targetDir}\n${
|
|
158
|
+
`Created ${summary.name} at ${targetDir}\n${written.length} files · ${summary.stack.join(' · ')}` +
|
|
121
159
|
(steps.length ? `\n${steps.join('; ')}` : ''),
|
|
122
160
|
);
|
|
123
161
|
}
|