@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.
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./fn/definePrompt.js";
2
+ export * from "./fn/defineResource.js";
3
+ export * from "./fn/defineTool.js";
4
+ export * from "./services/McpServerFactory.js";
@@ -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
+ }