@tsed/cli-mcp 7.0.0-beta.3
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/lib/esm/fn/definePrompt.js +52 -0
- package/lib/esm/fn/defineResource.js +34 -0
- package/lib/esm/fn/defineTool.js +69 -0
- package/lib/esm/index.js +4 -0
- package/lib/esm/interfaces/interfaces.js +1 -0
- package/lib/esm/services/CLIMCPServer.js +36 -0
- package/lib/esm/services/McpServerFactory.js +42 -0
- package/lib/esm/services/McpStdioServer.js +5 -0
- package/lib/esm/services/McpStreamableServer.js +28 -0
- package/lib/esm/utils/toZod.js +9 -0
- package/lib/tsconfig.esm.tsbuildinfo +1 -0
- package/lib/types/fn/definePrompt.d.ts +45 -0
- package/lib/types/fn/defineResource.d.ts +46 -0
- package/lib/types/fn/defineTool.d.ts +129 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/interfaces/interfaces.d.ts +13 -0
- package/lib/types/services/CLIMCPServer.d.ts +6 -0
- package/lib/types/services/McpServerFactory.d.ts +6 -0
- package/lib/types/services/McpStdioServer.d.ts +2 -0
- package/lib/types/services/McpStreamableServer.d.ts +2 -0
- package/lib/types/utils/toZod.d.ts +2 -0
- package/package.json +125 -0
- package/readme.md +241 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { injectable } from "@tsed/cli-core";
|
|
2
|
+
import { DIContext, injector, logger, runInContext } from "@tsed/di";
|
|
3
|
+
import { v4 } from "uuid";
|
|
4
|
+
/**
|
|
5
|
+
* Prompts are reusable templates that help humans prompt models to interact with your server.
|
|
6
|
+
* They're designed to be user-driven, and might appear as slash commands in a chat interface.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import {definePrompt} from "@tsed/cli-mcp";
|
|
10
|
+
*
|
|
11
|
+
* export default definePrompt({
|
|
12
|
+
* name: "review-code",
|
|
13
|
+
* title: 'Code review',
|
|
14
|
+
* description: 'Review code for best practices and potential issues',
|
|
15
|
+
* argsSchema: { code: z.string() }
|
|
16
|
+
* handler: ({ code }) => ({
|
|
17
|
+
* messages: [
|
|
18
|
+
* {
|
|
19
|
+
* role: 'user',
|
|
20
|
+
* content: {
|
|
21
|
+
* type: 'text',
|
|
22
|
+
* text: `Please review this code:\n\n${code}`
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ]
|
|
26
|
+
* })
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param options {PromptProps}
|
|
31
|
+
*/
|
|
32
|
+
export function definePrompt(options) {
|
|
33
|
+
const provider = injectable(options.token || Symbol.for(`MCP:RESOURCE:${options.name}`))
|
|
34
|
+
.type("CLI_MCP_RESOURCES")
|
|
35
|
+
.factory(() => ({
|
|
36
|
+
...options,
|
|
37
|
+
async handler(...args) {
|
|
38
|
+
const $ctx = new DIContext({
|
|
39
|
+
id: v4(),
|
|
40
|
+
injector: injector(),
|
|
41
|
+
logger: logger(),
|
|
42
|
+
level: logger().level,
|
|
43
|
+
maxStackSize: 0,
|
|
44
|
+
platform: "MCP"
|
|
45
|
+
});
|
|
46
|
+
return runInContext($ctx, () => {
|
|
47
|
+
return options.handler(...args);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
return provider.token();
|
|
52
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { injectable } from "@tsed/cli-core";
|
|
2
|
+
import { DIContext, injector, logger, runInContext } from "@tsed/di";
|
|
3
|
+
import { v4 } from "uuid";
|
|
4
|
+
export function defineResource(options) {
|
|
5
|
+
const provider = injectable(options.token || Symbol.for(`MCP:RESOURCE:${options.name}`))
|
|
6
|
+
.type("CLI_MCP_RESOURCES")
|
|
7
|
+
.factory(() => ({
|
|
8
|
+
...options,
|
|
9
|
+
async handler(...args) {
|
|
10
|
+
const $ctx = new DIContext({
|
|
11
|
+
id: v4(),
|
|
12
|
+
injector: injector(),
|
|
13
|
+
logger: logger(),
|
|
14
|
+
level: logger().level,
|
|
15
|
+
maxStackSize: 0,
|
|
16
|
+
platform: "MCP"
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
return await runInContext($ctx, () => {
|
|
20
|
+
return options.handler(...args);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
try {
|
|
25
|
+
await $ctx.destroy();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// ignore
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
return provider.token();
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { injectable } from "@tsed/cli-core";
|
|
2
|
+
import { DIContext, injector, logger, runInContext } from "@tsed/di";
|
|
3
|
+
import { JsonSchema } from "@tsed/schema";
|
|
4
|
+
import { v4 } from "uuid";
|
|
5
|
+
import { toZod } from "../utils/toZod.js";
|
|
6
|
+
/**
|
|
7
|
+
* Tools let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects.
|
|
8
|
+
* Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call, and the arguments.
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import {defineTool} from "@tsed/cli-mcp";
|
|
12
|
+
* import {s} from "@tsed/schema";
|
|
13
|
+
*
|
|
14
|
+
* export default defineTool({
|
|
15
|
+
* name: "my-tool",
|
|
16
|
+
* title: "My Tool",
|
|
17
|
+
* description: "My tool description",
|
|
18
|
+
* inputSchema: s.object({
|
|
19
|
+
* param1: s.string().required() // also support Zod
|
|
20
|
+
* }),
|
|
21
|
+
* outputSchema: s.object({
|
|
22
|
+
* result: s.string().required() // also support Zod
|
|
23
|
+
* }),
|
|
24
|
+
* handler(args) {
|
|
25
|
+
* return {
|
|
26
|
+
* content: [],
|
|
27
|
+
* structuredContent: {
|
|
28
|
+
* result: "Hello World!"
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @param options
|
|
35
|
+
*/
|
|
36
|
+
export function defineTool(options) {
|
|
37
|
+
const provider = injectable(options.token || Symbol.for(`MCP:TOOL:${options.name}`))
|
|
38
|
+
.type("CLI_MCP_TOOLS")
|
|
39
|
+
.factory(() => ({
|
|
40
|
+
...options,
|
|
41
|
+
inputSchema: toZod(options.inputSchema),
|
|
42
|
+
outputSchema: toZod(options.outputSchema),
|
|
43
|
+
async handler(args, extra) {
|
|
44
|
+
const $ctx = new DIContext({
|
|
45
|
+
id: v4(),
|
|
46
|
+
injector: injector(),
|
|
47
|
+
logger: logger(),
|
|
48
|
+
level: logger().level,
|
|
49
|
+
maxStackSize: 0,
|
|
50
|
+
platform: "MCP"
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
return await runInContext($ctx, () => {
|
|
54
|
+
return options.handler(args, extra);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
// Ensure per-invocation context is destroyed to avoid leaks
|
|
59
|
+
try {
|
|
60
|
+
await $ctx.destroy();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}));
|
|
68
|
+
return provider.token();
|
|
69
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "./McpServerFactory.js";
|
|
2
|
+
import { createInjector, loadPlugins } from "@tsed/cli-core";
|
|
3
|
+
import { constant, inject, injector } from "@tsed/di";
|
|
4
|
+
import { $asyncEmit } from "@tsed/hooks";
|
|
5
|
+
import { MCP_SERVER } from "./McpServerFactory.js";
|
|
6
|
+
export class CLIMCPServer {
|
|
7
|
+
constructor(settings) {
|
|
8
|
+
createInjector({
|
|
9
|
+
...settings,
|
|
10
|
+
logger: {
|
|
11
|
+
level: "info"
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
static async bootstrap(settings) {
|
|
16
|
+
const server = new CLIMCPServer({
|
|
17
|
+
...settings,
|
|
18
|
+
name: settings.name || "tsed",
|
|
19
|
+
project: {
|
|
20
|
+
srcDir: "src",
|
|
21
|
+
scriptsDir: "scripts",
|
|
22
|
+
...(settings.project || {})
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return server.bootstrap();
|
|
26
|
+
}
|
|
27
|
+
async bootstrap() {
|
|
28
|
+
constant("plugins") && (await loadPlugins());
|
|
29
|
+
await $asyncEmit("$beforeInit");
|
|
30
|
+
await injector().load();
|
|
31
|
+
await $asyncEmit("$afterInit");
|
|
32
|
+
injector().settings.set("loaded", true);
|
|
33
|
+
await $asyncEmit("$onReady");
|
|
34
|
+
await inject(MCP_SERVER).connect();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { constant, inject, injectable, logger } from "@tsed/di";
|
|
3
|
+
import { mcpStdioServer } from "./McpStdioServer.js";
|
|
4
|
+
import { mcpStreamableServer } from "./McpStreamableServer.js";
|
|
5
|
+
export const MCP_SERVER = injectable(McpServer)
|
|
6
|
+
.factory(() => {
|
|
7
|
+
const mode = constant("mcp.mode");
|
|
8
|
+
const name = constant("name");
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name,
|
|
11
|
+
version: constant("pkg.version")
|
|
12
|
+
});
|
|
13
|
+
const tools = constant("tools", []);
|
|
14
|
+
tools.map((token) => {
|
|
15
|
+
const { name, handler, ...opts } = inject(token);
|
|
16
|
+
server.registerTool(name, opts, handler);
|
|
17
|
+
});
|
|
18
|
+
const resources = constant("resources", []);
|
|
19
|
+
resources.map((token) => {
|
|
20
|
+
const { name, uri, template, handler, ...opts } = inject(token);
|
|
21
|
+
server.registerResource(name, (uri || template), opts, handler);
|
|
22
|
+
});
|
|
23
|
+
const prompts = constant("prompts", []);
|
|
24
|
+
prompts.map((token) => {
|
|
25
|
+
const { name, handler, ...opts } = inject(token);
|
|
26
|
+
server.registerPrompt(name, opts, handler);
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
server,
|
|
30
|
+
async connect() {
|
|
31
|
+
logger().info({ event: "MCP_SERVER_CONNECT" });
|
|
32
|
+
if (mode === "streamable-http") {
|
|
33
|
+
await mcpStreamableServer(server);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await mcpStdioServer(server);
|
|
37
|
+
}
|
|
38
|
+
logger().info({ event: "MCP_SERVER_CONNECTED" });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.token();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export async function mcpStreamableServer(server) {
|
|
2
|
+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
const { default: express } = await import("express");
|
|
5
|
+
const app = express();
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
app.post("/mcp", async (req, res) => {
|
|
8
|
+
// Create a new transport for each request to prevent request ID collisions
|
|
9
|
+
const transport = new StreamableHTTPServerTransport({
|
|
10
|
+
sessionIdGenerator: undefined,
|
|
11
|
+
enableJsonResponse: true
|
|
12
|
+
});
|
|
13
|
+
res.on("close", () => {
|
|
14
|
+
transport.close();
|
|
15
|
+
});
|
|
16
|
+
await server.connect(transport);
|
|
17
|
+
await transport.handleRequest(req, res, req.body);
|
|
18
|
+
});
|
|
19
|
+
const port = parseInt(process.env.PORT || "3000");
|
|
20
|
+
app
|
|
21
|
+
.listen(port, () => {
|
|
22
|
+
console.log(`Demo MCP Server running on http://localhost:${port}/mcp`);
|
|
23
|
+
})
|
|
24
|
+
.on("error", (error) => {
|
|
25
|
+
console.error("Server error:", error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { JsonSchema } from "@tsed/schema";
|
|
2
|
+
import { jsonSchemaToZod } from "json-schema-to-zod";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
function transform(schema) {
|
|
5
|
+
return eval(`(z) => ${jsonSchemaToZod(schema.toJSON()).replace("z.object", "")}`)(z);
|
|
6
|
+
}
|
|
7
|
+
export function toZod(schema) {
|
|
8
|
+
return schema && schema instanceof JsonSchema ? transform(schema) : schema;
|
|
9
|
+
}
|