@agimon-ai/imagine-mcp 0.2.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/LICENSE +52 -0
- package/README.md +144 -0
- package/dist/cli.cjs +129 -0
- package/dist/cli.d.cts +43 -0
- package/dist/cli.d.mts +43 -0
- package/dist/cli.mjs +125 -0
- package/dist/commands-BlRAon0l.cjs +83 -0
- package/dist/commands-c927CRZc.mjs +83 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.mts +150 -0
- package/dist/index.mjs +3 -0
- package/dist/stdio-CuIrQe3m.mjs +891 -0
- package/dist/stdio-D7OcvsZd.cjs +957 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: AgiFlow
|
|
6
|
+
Licensed Work: @agimon-ai/public-packages
|
|
7
|
+
The Licensed Work is (c) 2026 AgiFlow.
|
|
8
|
+
Additional Use Grant: None
|
|
9
|
+
Change Date: 2030-03-06
|
|
10
|
+
Change License: Apache License, Version 2.0
|
|
11
|
+
|
|
12
|
+
Terms
|
|
13
|
+
|
|
14
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
15
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
16
|
+
Licensor may make an Additional Use Grant, above, permitting limited
|
|
17
|
+
production use.
|
|
18
|
+
|
|
19
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
20
|
+
available distribution of a specific version of the Licensed Work under this
|
|
21
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
22
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
23
|
+
above terminate.
|
|
24
|
+
|
|
25
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
26
|
+
currently in effect as described in this License, you must purchase a
|
|
27
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
28
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
29
|
+
|
|
30
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
31
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
32
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
33
|
+
for each version of the Licensed Work released by Licensor.
|
|
34
|
+
|
|
35
|
+
You must conspicuously display this License on each original or modified copy
|
|
36
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
37
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
38
|
+
License apply to your use of that work.
|
|
39
|
+
|
|
40
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
41
|
+
terminate your rights under this License for the current and all other
|
|
42
|
+
versions of the Licensed Work.
|
|
43
|
+
|
|
44
|
+
This License does not grant you any right in any trademark or logo of
|
|
45
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
46
|
+
Licensor as expressly required by this License).
|
|
47
|
+
|
|
48
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
49
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
50
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
51
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
52
|
+
TITLE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# imagine-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for AI image generation and manipulation. Currently supports stock image search via Unsplash.
|
|
4
|
+
and local image reading from file path, URL, or data URI.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
### Stock Image Search (Unsplash)
|
|
9
|
+
- Search for high-quality stock photos from Unsplash
|
|
10
|
+
- Filter by orientation (landscape, portrait, squarish)
|
|
11
|
+
- Filter by color
|
|
12
|
+
- Pagination support
|
|
13
|
+
- Returns image URLs in multiple sizes (full, regular, small, thumbnail)
|
|
14
|
+
- Includes photographer attribution and links
|
|
15
|
+
|
|
16
|
+
### Image Reader
|
|
17
|
+
- Read raw image bytes from local file paths, HTTP(S) URLs, or data URIs
|
|
18
|
+
- Returns source metadata (`source`, `sourceType`, `mimeType`, `sizeBytes`, `sha256`)
|
|
19
|
+
- Optionally returns `base64` in the tool response
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
### Unsplash API Key
|
|
24
|
+
You need an Unsplash API access key to use the stock image search feature.
|
|
25
|
+
The `read-image` tool works without any upload or provider credentials.
|
|
26
|
+
|
|
27
|
+
1. Sign up at [Unsplash Developers](https://unsplash.com/developers)
|
|
28
|
+
2. Create a new application
|
|
29
|
+
3. Copy your Access Key
|
|
30
|
+
4. Set the environment variable: `export UNSPLASH_ACCESS_KEY=your_access_key_here`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Set your Unsplash API key
|
|
42
|
+
export UNSPLASH_ACCESS_KEY=your_access_key_here
|
|
43
|
+
|
|
44
|
+
# Run in development mode (stdio transport)
|
|
45
|
+
pnpm dev
|
|
46
|
+
|
|
47
|
+
# Run with HTTP transport
|
|
48
|
+
pnpm dev mcp-serve --type http --port 3000
|
|
49
|
+
|
|
50
|
+
# Run with SSE transport
|
|
51
|
+
pnpm dev mcp-serve --type sse --port 3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Build
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Test
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage with Claude Code
|
|
67
|
+
|
|
68
|
+
Add to your Claude Code configuration:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"imagine-mcp": {
|
|
74
|
+
"command": "node",
|
|
75
|
+
"args": ["/path/to/imagine-mcp/dist/cli.cjs", "mcp-serve"],
|
|
76
|
+
"env": {
|
|
77
|
+
"UNSPLASH_ACCESS_KEY": "your_access_key_here"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Available Tools
|
|
85
|
+
|
|
86
|
+
### unsplash_search
|
|
87
|
+
|
|
88
|
+
Search for stock images from Unsplash.
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
- `query` (required): Search query (e.g., "sunset", "technology", "nature")
|
|
92
|
+
- `perPage` (optional): Number of results per page (1-30, default: 10)
|
|
93
|
+
- `page` (optional): Page number for pagination (default: 1)
|
|
94
|
+
- `orientation` (optional): Filter by photo orientation ("landscape", "portrait", "squarish")
|
|
95
|
+
- `color` (optional): Filter by photo color ("black_and_white", "black", "white", "yellow", "orange", "red", "purple", "magenta", "green", "teal", "blue")
|
|
96
|
+
|
|
97
|
+
**Example usage in Claude Code:**
|
|
98
|
+
```
|
|
99
|
+
Search Unsplash for sunset photos
|
|
100
|
+
Search Unsplash for landscape technology images
|
|
101
|
+
Find portrait photos of people in blue tones
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Response includes:**
|
|
105
|
+
- Image ID and description
|
|
106
|
+
- Photographer name and profile
|
|
107
|
+
- Image dimensions and color
|
|
108
|
+
- URLs for different sizes (full, regular, small, thumbnail)
|
|
109
|
+
- Links to view on Unsplash and download
|
|
110
|
+
- Like count
|
|
111
|
+
|
|
112
|
+
### read-image
|
|
113
|
+
|
|
114
|
+
Read image data from different sources and return metadata.
|
|
115
|
+
|
|
116
|
+
**Parameters:**
|
|
117
|
+
- `source` (required): File path, URL, or data URI
|
|
118
|
+
- `includeBase64` (optional): Set true to include base64 output in response
|
|
119
|
+
|
|
120
|
+
**Response includes:**
|
|
121
|
+
- `source`: original source input (normalized path or URL)
|
|
122
|
+
- `sourceType`: `"file" | "url" | "data-uri"`
|
|
123
|
+
- `mimeType`: detected MIME type
|
|
124
|
+
- `sizeBytes`: byte length
|
|
125
|
+
- `sha256`: SHA-256 hash of bytes
|
|
126
|
+
|
|
127
|
+
## Adding New Tools
|
|
128
|
+
|
|
129
|
+
To add new image generation or manipulation tools:
|
|
130
|
+
|
|
131
|
+
1. Create a new tool:
|
|
132
|
+
```bash
|
|
133
|
+
# Use the scaffold feature
|
|
134
|
+
nx run imagine-mcp:list-scaffolds
|
|
135
|
+
# Or manually create in src/tools/
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
2. Register the tool in `src/server/index.ts`
|
|
139
|
+
|
|
140
|
+
3. Build and test
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
AGPL-3.0
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const require_stdio = require('./stdio-D7OcvsZd.cjs');
|
|
3
|
+
let commander = require("commander");
|
|
4
|
+
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
/**
|
|
7
|
+
* MCP Server Entry Point
|
|
8
|
+
*
|
|
9
|
+
* DESIGN PATTERNS:
|
|
10
|
+
* - CLI pattern with Commander for argument parsing
|
|
11
|
+
* - Lazy command loading for fast startup
|
|
12
|
+
* - Transport abstraction for multiple communication methods
|
|
13
|
+
*
|
|
14
|
+
* CODING STANDARDS:
|
|
15
|
+
* - Use async/await for asynchronous operations
|
|
16
|
+
* - Handle errors gracefully with try-catch
|
|
17
|
+
* - Log important events for debugging
|
|
18
|
+
* - Load commands lazily to minimize startup time
|
|
19
|
+
*
|
|
20
|
+
* AVOID:
|
|
21
|
+
* - Hardcoding command logic in index.ts (use separate command files)
|
|
22
|
+
* - Missing error handling for command execution
|
|
23
|
+
* - Eager loading of all commands at startup
|
|
24
|
+
*/
|
|
25
|
+
const packageJson = { version: "0.1.0" };
|
|
26
|
+
const commandDefinitions = [{
|
|
27
|
+
name: "mcp-serve",
|
|
28
|
+
description: "Start MCP server with specified transport",
|
|
29
|
+
loader: () => Promise.resolve().then(() => require("./commands-BlRAon0l.cjs")).then((m) => m.mcpServeCommand)
|
|
30
|
+
}];
|
|
31
|
+
/**
|
|
32
|
+
* Custom error for CLI execution failures with error codes and cause chaining.
|
|
33
|
+
*/
|
|
34
|
+
var CLIExecutionError = class extends Error {
|
|
35
|
+
code;
|
|
36
|
+
recovery;
|
|
37
|
+
constructor(message, code = "CLI_EXECUTION_FAILED", options) {
|
|
38
|
+
super(message, options);
|
|
39
|
+
this.name = "CLIExecutionError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.recovery = options?.recovery;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Error thrown when a command fails to load dynamically.
|
|
46
|
+
* Provides error code, command name context, and recovery suggestion.
|
|
47
|
+
*/
|
|
48
|
+
var CommandLoadError = class extends Error {
|
|
49
|
+
code = "COMMAND_LOAD_ERROR";
|
|
50
|
+
recovery;
|
|
51
|
+
commandName;
|
|
52
|
+
constructor(commandName, message, options) {
|
|
53
|
+
super(`Failed to load command '${commandName}': ${message}`, options);
|
|
54
|
+
this.name = "CommandLoadError";
|
|
55
|
+
this.commandName = commandName;
|
|
56
|
+
this.recovery = `Check that the command module exists and exports the expected command. Run with --help to see available commands.`;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Creates a lazy-loading command wrapper.
|
|
61
|
+
* The actual command is only imported when invoked.
|
|
62
|
+
* Includes error handling for dynamic import failures.
|
|
63
|
+
*/
|
|
64
|
+
function createLazyCommand(def) {
|
|
65
|
+
const command = new commander.Command(def.name).description(def.description).allowUnknownOption(true).allowExcessArguments(true);
|
|
66
|
+
command.action(async () => {
|
|
67
|
+
let realCommand;
|
|
68
|
+
try {
|
|
69
|
+
realCommand = await def.loader();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof CommandLoadError) throw error;
|
|
72
|
+
throw new CommandLoadError(def.name, error instanceof Error ? error.message : String(error), { cause: error });
|
|
73
|
+
}
|
|
74
|
+
const parentArgs = process.argv.slice(2);
|
|
75
|
+
await new commander.Command().addCommand(realCommand).parseAsync([
|
|
76
|
+
"node",
|
|
77
|
+
"imagine-mcp",
|
|
78
|
+
...parentArgs
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
return command;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Main entry point with lazy command loading for fast startup.
|
|
85
|
+
*/
|
|
86
|
+
async function main() {
|
|
87
|
+
const invokedCommand = process.argv[2] ?? "unknown";
|
|
88
|
+
try {
|
|
89
|
+
const program = new commander.Command();
|
|
90
|
+
program.name("imagine").description("MCP server for AI image generation and manipulation").version(packageJson.version);
|
|
91
|
+
for (const def of commandDefinitions) program.addCommand(createLazyCommand(def));
|
|
92
|
+
await program.parseAsync(process.argv);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof CLIExecutionError) {
|
|
95
|
+
process.stderr.write(`Error [${error.code}] ${JSON.stringify({
|
|
96
|
+
command: invokedCommand,
|
|
97
|
+
recovery: error.recovery,
|
|
98
|
+
message: error.message,
|
|
99
|
+
cause: error.cause instanceof Error ? error.cause.message : error.cause
|
|
100
|
+
})}\n`);
|
|
101
|
+
if (error.recovery) process.stderr.write(`Recovery: ${error.recovery}\n`);
|
|
102
|
+
if (error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
103
|
+
} else if (error instanceof CommandLoadError) {
|
|
104
|
+
process.stderr.write(`Error [${error.code}] ${JSON.stringify({
|
|
105
|
+
command: invokedCommand,
|
|
106
|
+
recovery: error.recovery,
|
|
107
|
+
message: error.message
|
|
108
|
+
})}\n`);
|
|
109
|
+
if (error.recovery) process.stderr.write(`Recovery: ${error.recovery}\n`);
|
|
110
|
+
if (error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
111
|
+
} else {
|
|
112
|
+
const errorCode = error.code ?? "CLI_EXECUTION_FAILED";
|
|
113
|
+
process.stderr.write(`Error [${errorCode}] ${JSON.stringify({
|
|
114
|
+
command: invokedCommand,
|
|
115
|
+
message: error instanceof Error ? error.message : String(error)
|
|
116
|
+
})}\n`);
|
|
117
|
+
process.stderr.write("Recovery: Run with --help for usage information.\n");
|
|
118
|
+
if (error instanceof Error && error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
main();
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
exports.CLIExecutionError = CLIExecutionError;
|
|
127
|
+
exports.CommandLoadError = CommandLoadError;
|
|
128
|
+
exports.commandDefinitions = commandDefinitions;
|
|
129
|
+
exports.createLazyCommand = createLazyCommand;
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
//#region src/cli.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Command metadata for lazy loading.
|
|
8
|
+
* Definitions are inline to avoid importing command modules at startup.
|
|
9
|
+
*/
|
|
10
|
+
interface CommandDef {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
loader: () => Promise<Command>;
|
|
14
|
+
}
|
|
15
|
+
declare const commandDefinitions: CommandDef[];
|
|
16
|
+
/**
|
|
17
|
+
* Custom error for CLI execution failures with error codes and cause chaining.
|
|
18
|
+
*/
|
|
19
|
+
declare class CLIExecutionError extends Error {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
readonly recovery?: string;
|
|
22
|
+
constructor(message: string, code?: string, options?: ErrorOptions & {
|
|
23
|
+
recovery?: string;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown when a command fails to load dynamically.
|
|
28
|
+
* Provides error code, command name context, and recovery suggestion.
|
|
29
|
+
*/
|
|
30
|
+
declare class CommandLoadError extends Error {
|
|
31
|
+
readonly code = "COMMAND_LOAD_ERROR";
|
|
32
|
+
readonly recovery: string;
|
|
33
|
+
readonly commandName: string;
|
|
34
|
+
constructor(commandName: string, message: string, options?: ErrorOptions);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a lazy-loading command wrapper.
|
|
38
|
+
* The actual command is only imported when invoked.
|
|
39
|
+
* Includes error handling for dynamic import failures.
|
|
40
|
+
*/
|
|
41
|
+
declare function createLazyCommand(def: CommandDef): Command;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { CLIExecutionError, CommandDef, CommandLoadError, commandDefinitions, createLazyCommand };
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
//#region src/cli.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Command metadata for lazy loading.
|
|
8
|
+
* Definitions are inline to avoid importing command modules at startup.
|
|
9
|
+
*/
|
|
10
|
+
interface CommandDef {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
loader: () => Promise<Command>;
|
|
14
|
+
}
|
|
15
|
+
declare const commandDefinitions: CommandDef[];
|
|
16
|
+
/**
|
|
17
|
+
* Custom error for CLI execution failures with error codes and cause chaining.
|
|
18
|
+
*/
|
|
19
|
+
declare class CLIExecutionError extends Error {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
readonly recovery?: string;
|
|
22
|
+
constructor(message: string, code?: string, options?: ErrorOptions & {
|
|
23
|
+
recovery?: string;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown when a command fails to load dynamically.
|
|
28
|
+
* Provides error code, command name context, and recovery suggestion.
|
|
29
|
+
*/
|
|
30
|
+
declare class CommandLoadError extends Error {
|
|
31
|
+
readonly code = "COMMAND_LOAD_ERROR";
|
|
32
|
+
readonly recovery: string;
|
|
33
|
+
readonly commandName: string;
|
|
34
|
+
constructor(commandName: string, message: string, options?: ErrorOptions);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a lazy-loading command wrapper.
|
|
38
|
+
* The actual command is only imported when invoked.
|
|
39
|
+
* Includes error handling for dynamic import failures.
|
|
40
|
+
*/
|
|
41
|
+
declare function createLazyCommand(def: CommandDef): Command;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { CLIExecutionError, CommandDef, CommandLoadError, commandDefinitions, createLazyCommand };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
/**
|
|
6
|
+
* MCP Server Entry Point
|
|
7
|
+
*
|
|
8
|
+
* DESIGN PATTERNS:
|
|
9
|
+
* - CLI pattern with Commander for argument parsing
|
|
10
|
+
* - Lazy command loading for fast startup
|
|
11
|
+
* - Transport abstraction for multiple communication methods
|
|
12
|
+
*
|
|
13
|
+
* CODING STANDARDS:
|
|
14
|
+
* - Use async/await for asynchronous operations
|
|
15
|
+
* - Handle errors gracefully with try-catch
|
|
16
|
+
* - Log important events for debugging
|
|
17
|
+
* - Load commands lazily to minimize startup time
|
|
18
|
+
*
|
|
19
|
+
* AVOID:
|
|
20
|
+
* - Hardcoding command logic in index.ts (use separate command files)
|
|
21
|
+
* - Missing error handling for command execution
|
|
22
|
+
* - Eager loading of all commands at startup
|
|
23
|
+
*/
|
|
24
|
+
const packageJson = { version: "0.1.0" };
|
|
25
|
+
const commandDefinitions = [{
|
|
26
|
+
name: "mcp-serve",
|
|
27
|
+
description: "Start MCP server with specified transport",
|
|
28
|
+
loader: () => import("./commands-c927CRZc.mjs").then((m) => m.mcpServeCommand)
|
|
29
|
+
}];
|
|
30
|
+
/**
|
|
31
|
+
* Custom error for CLI execution failures with error codes and cause chaining.
|
|
32
|
+
*/
|
|
33
|
+
var CLIExecutionError = class extends Error {
|
|
34
|
+
code;
|
|
35
|
+
recovery;
|
|
36
|
+
constructor(message, code = "CLI_EXECUTION_FAILED", options) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = "CLIExecutionError";
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.recovery = options?.recovery;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when a command fails to load dynamically.
|
|
45
|
+
* Provides error code, command name context, and recovery suggestion.
|
|
46
|
+
*/
|
|
47
|
+
var CommandLoadError = class extends Error {
|
|
48
|
+
code = "COMMAND_LOAD_ERROR";
|
|
49
|
+
recovery;
|
|
50
|
+
commandName;
|
|
51
|
+
constructor(commandName, message, options) {
|
|
52
|
+
super(`Failed to load command '${commandName}': ${message}`, options);
|
|
53
|
+
this.name = "CommandLoadError";
|
|
54
|
+
this.commandName = commandName;
|
|
55
|
+
this.recovery = `Check that the command module exists and exports the expected command. Run with --help to see available commands.`;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Creates a lazy-loading command wrapper.
|
|
60
|
+
* The actual command is only imported when invoked.
|
|
61
|
+
* Includes error handling for dynamic import failures.
|
|
62
|
+
*/
|
|
63
|
+
function createLazyCommand(def) {
|
|
64
|
+
const command = new Command(def.name).description(def.description).allowUnknownOption(true).allowExcessArguments(true);
|
|
65
|
+
command.action(async () => {
|
|
66
|
+
let realCommand;
|
|
67
|
+
try {
|
|
68
|
+
realCommand = await def.loader();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof CommandLoadError) throw error;
|
|
71
|
+
throw new CommandLoadError(def.name, error instanceof Error ? error.message : String(error), { cause: error });
|
|
72
|
+
}
|
|
73
|
+
const parentArgs = process.argv.slice(2);
|
|
74
|
+
await new Command().addCommand(realCommand).parseAsync([
|
|
75
|
+
"node",
|
|
76
|
+
"imagine-mcp",
|
|
77
|
+
...parentArgs
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
return command;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Main entry point with lazy command loading for fast startup.
|
|
84
|
+
*/
|
|
85
|
+
async function main() {
|
|
86
|
+
const invokedCommand = process.argv[2] ?? "unknown";
|
|
87
|
+
try {
|
|
88
|
+
const program = new Command();
|
|
89
|
+
program.name("imagine").description("MCP server for AI image generation and manipulation").version(packageJson.version);
|
|
90
|
+
for (const def of commandDefinitions) program.addCommand(createLazyCommand(def));
|
|
91
|
+
await program.parseAsync(process.argv);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof CLIExecutionError) {
|
|
94
|
+
process.stderr.write(`Error [${error.code}] ${JSON.stringify({
|
|
95
|
+
command: invokedCommand,
|
|
96
|
+
recovery: error.recovery,
|
|
97
|
+
message: error.message,
|
|
98
|
+
cause: error.cause instanceof Error ? error.cause.message : error.cause
|
|
99
|
+
})}\n`);
|
|
100
|
+
if (error.recovery) process.stderr.write(`Recovery: ${error.recovery}\n`);
|
|
101
|
+
if (error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
102
|
+
} else if (error instanceof CommandLoadError) {
|
|
103
|
+
process.stderr.write(`Error [${error.code}] ${JSON.stringify({
|
|
104
|
+
command: invokedCommand,
|
|
105
|
+
recovery: error.recovery,
|
|
106
|
+
message: error.message
|
|
107
|
+
})}\n`);
|
|
108
|
+
if (error.recovery) process.stderr.write(`Recovery: ${error.recovery}\n`);
|
|
109
|
+
if (error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
110
|
+
} else {
|
|
111
|
+
const errorCode = error.code ?? "CLI_EXECUTION_FAILED";
|
|
112
|
+
process.stderr.write(`Error [${errorCode}] ${JSON.stringify({
|
|
113
|
+
command: invokedCommand,
|
|
114
|
+
message: error instanceof Error ? error.message : String(error)
|
|
115
|
+
})}\n`);
|
|
116
|
+
process.stderr.write("Recovery: Run with --help for usage information.\n");
|
|
117
|
+
if (error instanceof Error && error.cause) process.stderr.write(`Caused by: ${error.cause instanceof Error ? error.cause.message : String(error.cause)}\n`);
|
|
118
|
+
}
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
main();
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { CLIExecutionError, CommandLoadError, commandDefinitions, createLazyCommand };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const require_stdio = require('./stdio-D7OcvsZd.cjs');
|
|
2
|
+
let commander = require("commander");
|
|
3
|
+
|
|
4
|
+
//#region src/types/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Transport mode types
|
|
7
|
+
*/
|
|
8
|
+
let TransportMode = /* @__PURE__ */ function(TransportMode$1) {
|
|
9
|
+
TransportMode$1["STDIO"] = "stdio";
|
|
10
|
+
TransportMode$1["HTTP"] = "http";
|
|
11
|
+
TransportMode$1["SSE"] = "sse";
|
|
12
|
+
return TransportMode$1;
|
|
13
|
+
}({});
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/commands/mcp-serve.ts
|
|
17
|
+
/**
|
|
18
|
+
* MCP Serve Command
|
|
19
|
+
*
|
|
20
|
+
* DESIGN PATTERNS:
|
|
21
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
22
|
+
* - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)
|
|
23
|
+
* - Factory pattern for creating transport handlers
|
|
24
|
+
* - Graceful shutdown pattern with signal handling
|
|
25
|
+
*
|
|
26
|
+
* CODING STANDARDS:
|
|
27
|
+
* - Use async/await for asynchronous operations
|
|
28
|
+
* - Implement proper error handling with try-catch blocks
|
|
29
|
+
* - Handle process signals for graceful shutdown
|
|
30
|
+
* - Provide clear CLI options and help messages
|
|
31
|
+
*
|
|
32
|
+
* AVOID:
|
|
33
|
+
* - Hardcoded configuration values (use CLI options or environment variables)
|
|
34
|
+
* - Missing error handling for transport startup
|
|
35
|
+
* - Not cleaning up resources on shutdown
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Start MCP server with given transport handler
|
|
39
|
+
*/
|
|
40
|
+
async function startServer(handler) {
|
|
41
|
+
await handler.start();
|
|
42
|
+
const shutdown = async (signal) => {
|
|
43
|
+
process.stderr.write(`\nReceived ${signal}, shutting down gracefully...\n`);
|
|
44
|
+
try {
|
|
45
|
+
await handler.stop();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
process.stderr.write(`Error during shutdown: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
53
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* MCP Serve command
|
|
57
|
+
*/
|
|
58
|
+
const mcpServeCommand = new commander.Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", "Transport type: stdio, http, or sse", "stdio").option("-p, --port <port>", "Port to listen on (http/sse only)", (val) => parseInt(val, 10), 3e3).option("--host <host>", "Host to bind to (http/sse only)", "localhost").action(async (options) => {
|
|
59
|
+
try {
|
|
60
|
+
const transportType = options.type.toLowerCase();
|
|
61
|
+
if (transportType === "stdio") await startServer(new require_stdio.StdioTransportHandler(require_stdio.createServer()));
|
|
62
|
+
else if (transportType === "http") await startServer(new require_stdio.HttpTransportHandler(() => require_stdio.createServer(), {
|
|
63
|
+
mode: TransportMode.HTTP,
|
|
64
|
+
port: options.port || Number(process.env.MCP_PORT) || 3e3,
|
|
65
|
+
host: options.host || process.env.MCP_HOST || "localhost"
|
|
66
|
+
}));
|
|
67
|
+
else if (transportType === "sse") await startServer(new require_stdio.SseTransportHandler(() => require_stdio.createServer(), {
|
|
68
|
+
mode: TransportMode.SSE,
|
|
69
|
+
port: options.port || Number(process.env.MCP_PORT) || 3e3,
|
|
70
|
+
host: options.host || process.env.MCP_HOST || "localhost"
|
|
71
|
+
}));
|
|
72
|
+
else {
|
|
73
|
+
process.stderr.write(`Unknown transport type: ${transportType}. Use: stdio, http, or sse\n`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
process.stderr.write(`Failed to start MCP server: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
exports.mcpServeCommand = mcpServeCommand;
|