@tsed/cli-mcp 7.0.0-beta.10
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 +85 -0
- package/lib/esm/index.js +4 -0
- package/lib/esm/interfaces/interfaces.js +1 -0
- package/lib/esm/services/McpServerFactory.js +41 -0
- package/lib/esm/services/McpStdioServer.js +7 -0
- package/lib/esm/services/McpStreamableServer.js +40 -0
- package/lib/esm/utils/toZod.js +9 -0
- package/lib/tsconfig.esm.tsbuildinfo +1 -0
- package/lib/types/fn/definePrompt.d.ts +43 -0
- package/lib/types/fn/defineResource.d.ts +45 -0
- package/lib/types/fn/defineTool.d.ts +157 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/interfaces/interfaces.d.ts +13 -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 +126 -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,85 @@
|
|
|
1
|
+
import { injectable } from "@tsed/cli-core";
|
|
2
|
+
import { isArrowFn } from "@tsed/core";
|
|
3
|
+
import { DIContext, injector, logger, runInContext } from "@tsed/di";
|
|
4
|
+
import { JsonSchema } from "@tsed/schema";
|
|
5
|
+
import { v4 } from "uuid";
|
|
6
|
+
import { toZod } from "../utils/toZod.js";
|
|
7
|
+
/**
|
|
8
|
+
* Tools let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects.
|
|
9
|
+
* Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call, and the arguments.
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import {defineTool} from "@tsed/cli-mcp";
|
|
13
|
+
* import {s} from "@tsed/schema";
|
|
14
|
+
*
|
|
15
|
+
* export default defineTool({
|
|
16
|
+
* name: "my-tool",
|
|
17
|
+
* title: "My Tool",
|
|
18
|
+
* description: "My tool description",
|
|
19
|
+
* inputSchema: s.object({
|
|
20
|
+
* param1: s.string().required() // also support Zod
|
|
21
|
+
* }),
|
|
22
|
+
* outputSchema: s.object({
|
|
23
|
+
* result: s.string().required() // also support Zod
|
|
24
|
+
* }),
|
|
25
|
+
* handler(args) {
|
|
26
|
+
* return {
|
|
27
|
+
* content: [],
|
|
28
|
+
* structuredContent: {
|
|
29
|
+
* result: "Hello World!"
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @param options
|
|
36
|
+
*/
|
|
37
|
+
export function defineTool(options) {
|
|
38
|
+
const provider = injectable(options.token || Symbol.for(`MCP:TOOL:${options.name}`))
|
|
39
|
+
.type("CLI_MCP_TOOLS")
|
|
40
|
+
.factory(() => ({
|
|
41
|
+
...options,
|
|
42
|
+
inputSchema: toZod(isArrowFn(options.inputSchema) ? options.inputSchema() : options.inputSchema),
|
|
43
|
+
outputSchema: toZod(options.outputSchema),
|
|
44
|
+
async handler(args, extra) {
|
|
45
|
+
const $ctx = new DIContext({
|
|
46
|
+
id: v4(),
|
|
47
|
+
injector: injector(),
|
|
48
|
+
logger: logger(),
|
|
49
|
+
level: logger().level,
|
|
50
|
+
maxStackSize: 0,
|
|
51
|
+
platform: "MCP"
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
return await runInContext($ctx, () => {
|
|
55
|
+
return options.handler(args, extra);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (er) {
|
|
59
|
+
$ctx.logger.error({
|
|
60
|
+
event: "MCP_TOOL_ERROR",
|
|
61
|
+
tool: options.name,
|
|
62
|
+
error_message: er.message,
|
|
63
|
+
stack: er.stack
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
content: [],
|
|
67
|
+
structuredContent: {
|
|
68
|
+
code: "E_MCP_TOOL_ERROR",
|
|
69
|
+
message: er.message
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
// Ensure per-invocation context is destroyed to avoid leaks
|
|
75
|
+
try {
|
|
76
|
+
await $ctx.destroy();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
return provider.token();
|
|
85
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
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 defaultMode = 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, handler, uri, template, ...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(mode = defaultMode) {
|
|
31
|
+
if (mode === "streamable-http") {
|
|
32
|
+
logger().info({ event: "MCP_SERVER_CONNECT", mode });
|
|
33
|
+
await mcpStreamableServer(server);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await mcpStdioServer(server);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.token();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { logger } from "@tsed/di";
|
|
2
|
+
export async function mcpStdioServer(server) {
|
|
3
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4
|
+
const transport = new StdioServerTransport();
|
|
5
|
+
logger().stop();
|
|
6
|
+
return server.connect(transport);
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logger } from "@tsed/di";
|
|
2
|
+
export async function mcpStreamableServer(server) {
|
|
3
|
+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
const { default: express } = await import("express");
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
app.post("/mcp", async (req, res) => {
|
|
9
|
+
// Create a new transport for each request to prevent request ID collisions
|
|
10
|
+
const transport = new StreamableHTTPServerTransport({
|
|
11
|
+
sessionIdGenerator: undefined,
|
|
12
|
+
enableJsonResponse: true
|
|
13
|
+
});
|
|
14
|
+
res.on("close", () => {
|
|
15
|
+
transport.close();
|
|
16
|
+
});
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
await transport.handleRequest(req, res, req.body);
|
|
19
|
+
});
|
|
20
|
+
const port = parseInt(process.env.PORT || "3000");
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
app
|
|
23
|
+
.listen(port, () => {
|
|
24
|
+
logger().info({
|
|
25
|
+
event: "MCP_STREAMABLE_SERVER",
|
|
26
|
+
state: "OK",
|
|
27
|
+
message: `Running http://localhost:${port}/mcp`
|
|
28
|
+
});
|
|
29
|
+
})
|
|
30
|
+
.on("close", () => resolve(true))
|
|
31
|
+
.on("error", (error) => {
|
|
32
|
+
logger().error({
|
|
33
|
+
event: "MCP_STREAMABLE_SERVER",
|
|
34
|
+
state: "KO",
|
|
35
|
+
message: error.message
|
|
36
|
+
});
|
|
37
|
+
reject(error);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -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
|
+
}
|