mcp-icon 1.0.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 +21 -0
- package/README.md +175 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +47 -0
- package/build/server.d.ts +7 -0
- package/build/server.js +72 -0
- package/build/services/index.d.ts +3 -0
- package/build/services/index.js +9 -0
- package/build/services/sse.d.ts +2 -0
- package/build/services/sse.js +48 -0
- package/build/services/stdio.d.ts +2 -0
- package/build/services/stdio.js +10 -0
- package/build/services/streamable.d.ts +2 -0
- package/build/services/streamable.js +42 -0
- package/build/tools/search-icons.d.ts +2 -0
- package/build/tools/search-icons.js +24 -0
- package/build/utils/api.d.ts +9 -0
- package/build/utils/api.js +23 -0
- package/build/utils/logger.d.ts +13 -0
- package/build/utils/logger.js +27 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hustcc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐จ mcp-icon
|
|
4
|
+
|
|
5
|
+
**A Model Context Protocol (MCP) server for semantic SVG icon search.**
|
|
6
|
+
|
|
7
|
+
Generate infographic SVG icons by keyword โ over **100,000 icons** with semantic search support, powered by [AntV Infographic](https://infographic.antv.vision/icon).
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/mcp-icon)
|
|
10
|
+
[](https://github.com/hustcc/mcp-icon/actions/workflows/build.yml)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ๐ Table of Contents
|
|
18
|
+
|
|
19
|
+
- [โจ Features](#-features)
|
|
20
|
+
- [๐ค Usage](#-usage)
|
|
21
|
+
- [๐ฐ Transport Modes](#-transport-modes)
|
|
22
|
+
- [๐ฎ CLI Options](#-cli-options)
|
|
23
|
+
- [๐จ Development](#-development)
|
|
24
|
+
- [๐ License](#-license)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## โจ Features
|
|
29
|
+
|
|
30
|
+
- ๐ **Semantic search** โ Find icons by meaning, not just exact names
|
|
31
|
+
- ๐ผ๏ธ **100,000+ SVG icons** โ A massive library of high-quality infographic icons
|
|
32
|
+
- โก **Three transport modes** โ `stdio`, `sse`, and `streamable-http`
|
|
33
|
+
- ๐ชถ **Minimal dependencies** โ Clean, focused implementation
|
|
34
|
+
- ๐งช **Fully tested** โ Unit tests with Vitest
|
|
35
|
+
|
|
36
|
+
### Available Tool
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `search_icons` | Search for SVG icons by keyword. Returns a list of SVG URLs matching the semantic query. |
|
|
41
|
+
|
|
42
|
+
**`search_icons` parameters:**
|
|
43
|
+
|
|
44
|
+
| Parameter | Type | Required | Default | Description |
|
|
45
|
+
|-----------|------|----------|---------|-------------|
|
|
46
|
+
| `keyword` | string | โ
| โ | Search keyword or phrase (e.g. `"data analysis"`, `"cloud"`) |
|
|
47
|
+
| `topK` | number | โ | `3` | Number of icons to return (1โ20) |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ๐ค Usage
|
|
52
|
+
|
|
53
|
+
Add to your MCP client configuration (e.g. Claude Desktop, VS Code, Cursor):
|
|
54
|
+
|
|
55
|
+
**macOS / Linux:**
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"mcp-icon": {
|
|
61
|
+
"command": "npx",
|
|
62
|
+
"args": ["-y", "mcp-icon"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Windows:**
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"mcp-icon": {
|
|
74
|
+
"command": "cmd",
|
|
75
|
+
"args": ["/c", "npx", "-y", "mcp-icon"]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ๐ฐ Transport Modes
|
|
84
|
+
|
|
85
|
+
`mcp-icon` supports three standard MCP transport protocols.
|
|
86
|
+
|
|
87
|
+
### stdio (default)
|
|
88
|
+
|
|
89
|
+
Used by desktop MCP clients (Claude Desktop, Cursor, etc.):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx mcp-icon
|
|
93
|
+
# or explicitly:
|
|
94
|
+
npx mcp-icon --transport stdio
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### SSE (Server-Sent Events)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx mcp-icon --transport sse --port 3456
|
|
101
|
+
# Server available at: http://localhost:3456/sse
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Streamable HTTP
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx mcp-icon --transport streamable --port 3456
|
|
108
|
+
# Server available at: http://localhost:3456/mcp
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## ๐ฎ CLI Options
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
mcp-icon CLI
|
|
117
|
+
|
|
118
|
+
Options:
|
|
119
|
+
--transport, -t Transport protocol: "stdio", "sse", or "streamable" (default: "stdio")
|
|
120
|
+
--host, -h Host for SSE or streamable transport (default: localhost)
|
|
121
|
+
--port, -p Port for SSE or streamable transport (default: 3456)
|
|
122
|
+
--endpoint, -e Endpoint path:
|
|
123
|
+
- For SSE: default is "/sse"
|
|
124
|
+
- For streamable: default is "/mcp"
|
|
125
|
+
--help, -H Show this help message
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ๐จ Development
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Clone the repository
|
|
134
|
+
git clone https://github.com/hustcc/mcp-icon.git
|
|
135
|
+
cd mcp-icon
|
|
136
|
+
|
|
137
|
+
# Install dependencies
|
|
138
|
+
npm install
|
|
139
|
+
|
|
140
|
+
# Build
|
|
141
|
+
npm run build
|
|
142
|
+
|
|
143
|
+
# Run tests
|
|
144
|
+
npm test
|
|
145
|
+
|
|
146
|
+
# Start with MCP inspector (for local debugging)
|
|
147
|
+
npm start
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Project Structure
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
src/
|
|
154
|
+
โโโ index.ts # CLI entry point
|
|
155
|
+
โโโ server.ts # MCP server + tool handlers
|
|
156
|
+
โโโ services/
|
|
157
|
+
โ โโโ stdio.ts # Stdio transport
|
|
158
|
+
โ โโโ sse.ts # SSE transport
|
|
159
|
+
โ โโโ streamable.ts # Streamable HTTP transport
|
|
160
|
+
โโโ tools/
|
|
161
|
+
โ โโโ search-icons.ts # Tool definition
|
|
162
|
+
โโโ utils/
|
|
163
|
+
โโโ api.ts # Icon search API client
|
|
164
|
+
โโโ logger.ts # Logger utility
|
|
165
|
+
tests/
|
|
166
|
+
โโโ api.test.ts # API client unit tests
|
|
167
|
+
โโโ server.test.ts # MCP server integration tests
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ๐ License
|
|
173
|
+
|
|
174
|
+
MIT ยฉ [hustcc](https://github.com/hustcc)
|
|
175
|
+
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const logger_1 = require("./utils/logger.js");
|
|
6
|
+
const server_1 = require("./server.js");
|
|
7
|
+
const { values } = (0, node_util_1.parseArgs)({
|
|
8
|
+
options: {
|
|
9
|
+
transport: { type: "string", short: "t", default: "stdio" },
|
|
10
|
+
host: { type: "string", short: "h", default: "localhost" },
|
|
11
|
+
port: { type: "string", short: "p", default: "3456" },
|
|
12
|
+
endpoint: { type: "string", short: "e", default: "" },
|
|
13
|
+
help: { type: "boolean", short: "H" },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (values.help) {
|
|
17
|
+
console.log(`
|
|
18
|
+
mcp-icon CLI
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--transport, -t Transport protocol: "stdio", "sse", or "streamable" (default: "stdio")
|
|
22
|
+
--host, -h Host for SSE or streamable transport (default: localhost)
|
|
23
|
+
--port, -p Port for SSE or streamable transport (default: 3456)
|
|
24
|
+
--endpoint, -e Endpoint path:
|
|
25
|
+
- For SSE: default is "/sse"
|
|
26
|
+
- For streamable: default is "/mcp"
|
|
27
|
+
--help, -H Show this help message
|
|
28
|
+
`);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
const transport = values.transport.toLowerCase();
|
|
32
|
+
const port = Number.parseInt(values.port, 10);
|
|
33
|
+
const host = values.host;
|
|
34
|
+
if (transport === "sse") {
|
|
35
|
+
logger_1.logger.setIsStdio(false);
|
|
36
|
+
const endpoint = values.endpoint || "/sse";
|
|
37
|
+
(0, server_1.runSSEServer)(host, port, endpoint).catch(console.error);
|
|
38
|
+
}
|
|
39
|
+
else if (transport === "streamable") {
|
|
40
|
+
logger_1.logger.setIsStdio(false);
|
|
41
|
+
const endpoint = values.endpoint || "/mcp";
|
|
42
|
+
(0, server_1.runStreamableServer)(host, port, endpoint).catch(console.error);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
logger_1.logger.setIsStdio(true);
|
|
46
|
+
(0, server_1.runStdioServer)().catch(console.error);
|
|
47
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { startSSEServer, startStdioServer, startStreamableServer } from "./services/index";
|
|
3
|
+
export { startSSEServer, startStdioServer, startStreamableServer };
|
|
4
|
+
export declare function createServer(): Server;
|
|
5
|
+
export declare function runStdioServer(): Promise<void>;
|
|
6
|
+
export declare function runSSEServer(host?: string, port?: number, endpoint?: string): Promise<void>;
|
|
7
|
+
export declare function runStreamableServer(host?: string, port?: number, endpoint?: string): Promise<void>;
|
package/build/server.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startStreamableServer = exports.startStdioServer = exports.startSSEServer = void 0;
|
|
4
|
+
exports.createServer = createServer;
|
|
5
|
+
exports.runStdioServer = runStdioServer;
|
|
6
|
+
exports.runSSEServer = runSSEServer;
|
|
7
|
+
exports.runStreamableServer = runStreamableServer;
|
|
8
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
9
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
10
|
+
const search_icons_1 = require("./tools/search-icons.js");
|
|
11
|
+
const api_1 = require("./utils/api.js");
|
|
12
|
+
const logger_1 = require("./utils/logger.js");
|
|
13
|
+
const index_1 = require("./services/index.js");
|
|
14
|
+
Object.defineProperty(exports, "startSSEServer", { enumerable: true, get: function () { return index_1.startSSEServer; } });
|
|
15
|
+
Object.defineProperty(exports, "startStdioServer", { enumerable: true, get: function () { return index_1.startStdioServer; } });
|
|
16
|
+
Object.defineProperty(exports, "startStreamableServer", { enumerable: true, get: function () { return index_1.startStreamableServer; } });
|
|
17
|
+
function createServer() {
|
|
18
|
+
const server = new index_js_1.Server({ name: "mcp-icon", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
19
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
20
|
+
tools: [search_icons_1.searchIconsTool],
|
|
21
|
+
}));
|
|
22
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
23
|
+
const { name, arguments: args = {} } = request.params;
|
|
24
|
+
if (name !== "search_icons") {
|
|
25
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
26
|
+
}
|
|
27
|
+
const { keyword, topK = 3 } = args;
|
|
28
|
+
if (!keyword || typeof keyword !== "string") {
|
|
29
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "The 'keyword' parameter is required and must be a string.");
|
|
30
|
+
}
|
|
31
|
+
const clampedTopK = Math.min(20, Math.max(1, Math.round(topK)));
|
|
32
|
+
logger_1.logger.info(`Searching icons: keyword="${keyword}", topK=${clampedTopK}`);
|
|
33
|
+
try {
|
|
34
|
+
const { urls } = await (0, api_1.searchIcons)(keyword, clampedTopK);
|
|
35
|
+
return {
|
|
36
|
+
content: urls.map((url) => ({
|
|
37
|
+
type: "text",
|
|
38
|
+
text: url,
|
|
39
|
+
})),
|
|
40
|
+
_meta: {
|
|
41
|
+
description: "SVG icon URLs matching the search keyword. Each URL points to an SVG file that can be embedded using an <img> tag or rendered inline.",
|
|
42
|
+
urls,
|
|
43
|
+
keyword,
|
|
44
|
+
topK: clampedTopK,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
50
|
+
logger_1.logger.error(`Failed to search icons: ${message}`);
|
|
51
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to search icons: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
server.onerror = (err) => {
|
|
55
|
+
logger_1.logger.error("Server error", err);
|
|
56
|
+
};
|
|
57
|
+
process.on("SIGINT", async () => {
|
|
58
|
+
await server.close();
|
|
59
|
+
process.exit(0);
|
|
60
|
+
});
|
|
61
|
+
return server;
|
|
62
|
+
}
|
|
63
|
+
async function runStdioServer() {
|
|
64
|
+
const server = createServer();
|
|
65
|
+
await (0, index_1.startStdioServer)(server);
|
|
66
|
+
}
|
|
67
|
+
async function runSSEServer(host = "localhost", port = 3456, endpoint = "/sse") {
|
|
68
|
+
await (0, index_1.startSSEServer)(createServer, endpoint, port, host);
|
|
69
|
+
}
|
|
70
|
+
async function runStreamableServer(host = "localhost", port = 3456, endpoint = "/mcp") {
|
|
71
|
+
await (0, index_1.startStreamableServer)(createServer, endpoint, port, host);
|
|
72
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startStreamableServer = exports.startSSEServer = exports.startStdioServer = void 0;
|
|
4
|
+
var stdio_1 = require("./stdio.js");
|
|
5
|
+
Object.defineProperty(exports, "startStdioServer", { enumerable: true, get: function () { return stdio_1.startStdioServer; } });
|
|
6
|
+
var sse_1 = require("./sse.js");
|
|
7
|
+
Object.defineProperty(exports, "startSSEServer", { enumerable: true, get: function () { return sse_1.startSSEServer; } });
|
|
8
|
+
var streamable_1 = require("./streamable.js");
|
|
9
|
+
Object.defineProperty(exports, "startStreamableServer", { enumerable: true, get: function () { return streamable_1.startStreamableServer; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startSSEServer = startSSEServer;
|
|
7
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const logger_1 = require("../utils/logger.js");
|
|
10
|
+
async function startSSEServer(createServer, endpoint = "/sse", port = 3456, host = "localhost") {
|
|
11
|
+
const app = (0, express_1.default)();
|
|
12
|
+
app.use(express_1.default.json());
|
|
13
|
+
const connections = {};
|
|
14
|
+
app.get(endpoint, async (_req, res) => {
|
|
15
|
+
const server = createServer();
|
|
16
|
+
const transport = new sse_js_1.SSEServerTransport("/messages", res);
|
|
17
|
+
connections[transport.sessionId] = transport;
|
|
18
|
+
transport.onclose = () => {
|
|
19
|
+
delete connections[transport.sessionId];
|
|
20
|
+
logger_1.logger.info(`SSE connection closed: sessionId=${transport.sessionId}`);
|
|
21
|
+
};
|
|
22
|
+
await server.connect(transport);
|
|
23
|
+
logger_1.logger.info(`SSE connection opened: sessionId=${transport.sessionId}`);
|
|
24
|
+
});
|
|
25
|
+
app.post("/messages", async (req, res) => {
|
|
26
|
+
const sessionId = req.query.sessionId;
|
|
27
|
+
if (!sessionId) {
|
|
28
|
+
res.status(400).send("Missing sessionId parameter");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const transport = connections[sessionId];
|
|
32
|
+
if (!transport) {
|
|
33
|
+
res.status(404).send("Session not found");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
logger_1.logger.error("SSE error handling message", e);
|
|
41
|
+
if (!res.headersSent)
|
|
42
|
+
res.status(500).send("Error handling request");
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
app.listen(port, host, () => {
|
|
46
|
+
logger_1.logger.success(`SSE server listening on http://${host}:${port}${endpoint}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startStdioServer = startStdioServer;
|
|
4
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const logger_1 = require("../utils/logger.js");
|
|
6
|
+
async function startStdioServer(server) {
|
|
7
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
8
|
+
await server.connect(transport);
|
|
9
|
+
logger_1.logger.success("Stdio MCP server started");
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startStreamableServer = startStreamableServer;
|
|
7
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
8
|
+
const cors_1 = __importDefault(require("cors"));
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const logger_1 = require("../utils/logger.js");
|
|
11
|
+
async function startStreamableServer(createServer, endpoint = "/mcp", port = 3456, host = "localhost") {
|
|
12
|
+
const app = (0, express_1.default)();
|
|
13
|
+
app.use(express_1.default.json());
|
|
14
|
+
app.use((0, cors_1.default)({ origin: "*", exposedHeaders: ["Mcp-Session-Id"] }));
|
|
15
|
+
app.post(endpoint, async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const server = createServer();
|
|
18
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
19
|
+
sessionIdGenerator: undefined,
|
|
20
|
+
enableJsonResponse: true,
|
|
21
|
+
});
|
|
22
|
+
res.on("close", () => {
|
|
23
|
+
transport.close();
|
|
24
|
+
});
|
|
25
|
+
await server.connect(transport);
|
|
26
|
+
await transport.handleRequest(req, res, req.body);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
logger_1.logger.error("Streamable HTTP error", e);
|
|
30
|
+
if (!res.headersSent) {
|
|
31
|
+
res.status(500).json({
|
|
32
|
+
jsonrpc: "2.0",
|
|
33
|
+
error: { code: -32603, message: "Internal server error" },
|
|
34
|
+
id: null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
app.listen(port, host, () => {
|
|
40
|
+
logger_1.logger.success(`Streamable HTTP server listening on http://${host}:${port}${endpoint}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchIconsTool = void 0;
|
|
4
|
+
exports.searchIconsTool = {
|
|
5
|
+
name: "search_icons",
|
|
6
|
+
description: "Search for SVG icons by keyword using semantic search. Returns URLs of matching SVG icons from a library of over 100,000 infographic icons.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
keyword: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: 'The search keyword or phrase to find relevant icons (e.g. "data analysis", "cloud computing", "security").',
|
|
13
|
+
},
|
|
14
|
+
topK: {
|
|
15
|
+
type: "number",
|
|
16
|
+
description: "The number of icons to return. Must be between 1 and 20. Defaults to 3.",
|
|
17
|
+
default: 3,
|
|
18
|
+
minimum: 1,
|
|
19
|
+
maximum: 20,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: ["keyword"],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface IconSearchResult {
|
|
2
|
+
urls: string[];
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Search for icons by keyword using the Infographic icon API.
|
|
6
|
+
* @param text - The search keyword (e.g. "data analysis")
|
|
7
|
+
* @param topK - Number of icons to return (1-20, default 3)
|
|
8
|
+
*/
|
|
9
|
+
export declare function searchIcons(text: string, topK?: number): Promise<IconSearchResult>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchIcons = searchIcons;
|
|
4
|
+
const ICON_API_BASE = "https://www.weavefox.cn/api/open/v1/icon";
|
|
5
|
+
/**
|
|
6
|
+
* Search for icons by keyword using the Infographic icon API.
|
|
7
|
+
* @param text - The search keyword (e.g. "data analysis")
|
|
8
|
+
* @param topK - Number of icons to return (1-20, default 3)
|
|
9
|
+
*/
|
|
10
|
+
async function searchIcons(text, topK = 3) {
|
|
11
|
+
const url = `${ICON_API_BASE}?text=${encodeURIComponent(text)}&topK=${topK}`;
|
|
12
|
+
const response = await fetch(url, {
|
|
13
|
+
headers: { "Content-Type": "application/json" },
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Icon API request failed: ${response.status} ${response.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
const json = (await response.json());
|
|
19
|
+
if (!json.status || !json.data?.success) {
|
|
20
|
+
throw new Error(`Icon API returned an error: ${json.message}`);
|
|
21
|
+
}
|
|
22
|
+
return { urls: json.data.data };
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare function setIsStdio(isStdio: boolean): void;
|
|
2
|
+
declare function info(message: string, ...args: unknown[]): void;
|
|
3
|
+
declare function warn(message: string, ...args: unknown[]): void;
|
|
4
|
+
declare function error(message: string, err?: unknown): void;
|
|
5
|
+
declare function success(message: string, ...args: unknown[]): void;
|
|
6
|
+
export declare const logger: {
|
|
7
|
+
info: typeof info;
|
|
8
|
+
warn: typeof warn;
|
|
9
|
+
error: typeof error;
|
|
10
|
+
success: typeof success;
|
|
11
|
+
setIsStdio: typeof setIsStdio;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logger = void 0;
|
|
4
|
+
const PREFIX = "[mcp-icon]";
|
|
5
|
+
let IS_STDIO = true;
|
|
6
|
+
function setIsStdio(isStdio) {
|
|
7
|
+
IS_STDIO = isStdio;
|
|
8
|
+
}
|
|
9
|
+
function getTimestamp() {
|
|
10
|
+
return new Date().toISOString();
|
|
11
|
+
}
|
|
12
|
+
function info(message, ...args) {
|
|
13
|
+
const fn = IS_STDIO ? console.error : console.log;
|
|
14
|
+
fn(`${PREFIX} ${getTimestamp()} โน๏ธ ${message}`, ...args);
|
|
15
|
+
}
|
|
16
|
+
function warn(message, ...args) {
|
|
17
|
+
const fn = IS_STDIO ? console.warn : console.log;
|
|
18
|
+
fn(`${PREFIX} ${getTimestamp()} โ ๏ธ ${message}`, ...args);
|
|
19
|
+
}
|
|
20
|
+
function error(message, err) {
|
|
21
|
+
console.error(`${PREFIX} ${getTimestamp()} โ ${message}`, err ?? "");
|
|
22
|
+
}
|
|
23
|
+
function success(message, ...args) {
|
|
24
|
+
const fn = IS_STDIO ? console.error : console.log;
|
|
25
|
+
fn(`${PREFIX} ${getTimestamp()} โ
${message}`, ...args);
|
|
26
|
+
}
|
|
27
|
+
exports.logger = { info, warn, error, success, setIsStdio };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-icon",
|
|
3
|
+
"description": "A Model Context Protocol server for semantic icon search. Generate infographic SVG icons by keyword, with over 100,000 icons and semantic search support.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "npx @modelcontextprotocol/inspector node build/index.js",
|
|
8
|
+
"prebuild": "rm -rf build/*",
|
|
9
|
+
"build": "tsc && tsc-alias -p tsconfig.json",
|
|
10
|
+
"postbuild": "chmod +x build/index.js",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"mcp-icon": "./build/index.js"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"registry": "https://registry.npmjs.org/",
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"files": ["build"],
|
|
23
|
+
"keywords": ["antv", "mcp", "icon", "svg", "search", "infographic"],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/hustcc/mcp-icon"
|
|
27
|
+
},
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "hustcc"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
37
|
+
"cors": "^2.8.5",
|
|
38
|
+
"express": "^5.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/cors": "^2.8.19",
|
|
42
|
+
"@types/express": "^5.0.3",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
45
|
+
"tsc-alias": "^1.8.0",
|
|
46
|
+
"typescript": "^5.8.0",
|
|
47
|
+
"vitest": "^3.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|