kleanmcp 1.0.1 → 1.0.2

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/example/index.ts DELETED
@@ -1,125 +0,0 @@
1
- import { Elysia } from 'elysia'
2
- import cors from '@elysiajs/cors'
3
- import {
4
- type CallToolResult,
5
- type JSONRPCErrorResponse,
6
- type ToolDefinitionGeneric,
7
- type ToolSet,
8
- type JSONRPCResponse,
9
- ToolDefinition,
10
- } from 'leanmcp/types'
11
- import { treaty } from '@elysiajs/eden';
12
- import {
13
- MCPServerService,
14
- elysiaJsonRPC
15
- } from 'leanmcp';
16
-
17
- export const Tool1 = ToolDefinition(
18
- "tool_name_1",
19
- "this tool is a test tool 1",
20
- [
21
- {
22
- name: "arg1",
23
- description: "describing arg1",
24
- type: "string",
25
- required: false,
26
- },
27
- ] as const,
28
- )(async (
29
- _reference: object,
30
- arg_string: string,
31
- _extra?: any,
32
- ): Promise<CallToolResult> => {
33
- return {
34
- content: [
35
- {
36
- type: "text",
37
- text: arg_string,
38
- },
39
- ],
40
- };
41
- });
42
-
43
- export const Tool2 = ToolDefinition(
44
- "tool_name_2",
45
- "this tool is a test tool 2",
46
- [
47
- {
48
- name: "arg1",
49
- description: "describing arg1",
50
- type: "string",
51
- required: false,
52
- },
53
- {
54
- name: "arg2",
55
- description: "describing arg2",
56
- type: "boolean",
57
- required: false,
58
- },
59
- ] as const,
60
- )(async (
61
- _reference: any,
62
- arg1: string,
63
- arg2: boolean,
64
- _extra?: any,
65
- ): Promise<CallToolResult> => {
66
- throw new Error("this always fails");
67
- });
68
-
69
- export class TestToolSet implements ToolSet {
70
- getTools(): ToolDefinitionGeneric[] {
71
- return [Tool1, Tool2];
72
- }
73
- getTool(toolName: string): ToolDefinitionGeneric | undefined {
74
- return this.getTools().find(x => x.name == toolName);
75
- }
76
- }
77
-
78
-
79
- const serverService = new MCPServerService(
80
- "testServerTitle",
81
- "1.0.0",
82
- "No instructions",
83
- (toolName: string) => {
84
- return serverService;
85
- },
86
- [new TestToolSet()],
87
- );
88
-
89
- export const app = new Elysia().use(cors({
90
- origin: "*",
91
- methods: ["GET", "POST", "OPTIONS"],
92
- allowedHeaders: [
93
- "Content-Type",
94
- "Authorization",
95
- "mcp-protocol-version",
96
- "mcp-session-id",
97
- ]
98
- })).use(elysiaJsonRPC({
99
- debug: true,
100
- dns_rebind_origin: undefined,
101
- service: serverService,
102
- onError: (res: JSONRPCErrorResponse) => {
103
- // console.log("onError", res.error.data);
104
- },
105
- onResponse: (res: any) => {
106
- // console.log("onResponse");
107
- },
108
- onRequest: (res: any) => {
109
- // console.log("onRequest");
110
- },
111
- }));
112
-
113
- const client = treaty<typeof app>(app);
114
- const resp = await client["stream"].post({
115
- id: "1",
116
- jsonrpc: "2.0",
117
- method: "tools/list",
118
- });
119
-
120
- const response = resp.data as JSONRPCResponse;
121
- if (response.id == "1" && response.jsonrpc == "2.0" && response.result) {
122
- console.log("works!")
123
- } else {
124
- console.error("failed!")
125
- }
@@ -1,19 +0,0 @@
1
- {
2
- "name": "test_leanmcp",
3
- "module": "index.ts",
4
- "type": "module",
5
- "private": true,
6
- "devDependencies": {
7
- "@types/bun": "latest"
8
- },
9
- "peerDependencies": {
10
- "typescript": "^5"
11
- },
12
- "dependencies": {
13
- "@elysiajs/cors": "^1.4.1",
14
- "@elysiajs/eden": "^1.4.8",
15
- "elysia": "^1.4.25",
16
- "leanmcp": "link:leanmcp",
17
- "zod": "^4.3.6"
18
- }
19
- }
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
18
- "strict": true,
19
- "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
- "noImplicitOverride": true,
23
-
24
- // Some stricter flags (disabled by default)
25
- "noUnusedLocals": false,
26
- "noUnusedParameters": false,
27
- "noPropertyAccessFromIndexSignature": false
28
- }
29
- }
package/src/index.ts DELETED
@@ -1,131 +0,0 @@
1
-
2
- import {
3
- InputSchemaFromParameters,
4
- ToolArgsToParameterList,
5
- type CallToolResult,
6
- type Tool,
7
- type ToolDefinitionGeneric,
8
- type ToolSet
9
- } from "./types";
10
-
11
- import {
12
- MCPError,
13
- type InitializeParams,
14
- type InitializeResult,
15
- type MCPServerInterface,
16
- type PingParams,
17
- type PingResult,
18
- type PromptsGetParams,
19
- type PromptsGetResult,
20
- type PromptsListParams,
21
- type PromptsListResult,
22
- type ResourcesListParams,
23
- type ResourcesListResult,
24
- type ResourcesReadParams,
25
- type ResourcesReadResult,
26
- type ResourcesSubscribeParams,
27
- type ResourcesSubscribeResult,
28
- type ResourcesTemplatesListParams,
29
- type ResourcesTemplatesListResult,
30
- type ToolsCallParams,
31
- type ToolsCallResult,
32
- type ToolsListParams,
33
- type ToolsListResult,
34
- } from "elysia-jsonrpc/mcp";
35
-
36
- import elysiaJsonRPC from "elysia-jsonrpc";
37
- export { elysiaJsonRPC };
38
-
39
- export type ReferenceForToolCallFunction = (toolName: string) => any;
40
-
41
- export class MCPServerService implements MCPServerInterface {
42
- constructor(
43
- readonly serverTitle: string,
44
- readonly serverVersion: string,
45
- readonly serverInstructions: string,
46
- readonly getReferenceInstanceForToolCall: ReferenceForToolCallFunction,
47
- readonly toolsets: ToolSet[],
48
- ) {}
49
- initialize(params: InitializeParams): Promise<InitializeResult> {
50
- return Promise.resolve({
51
- capabilities: {
52
- tools: {},
53
- },
54
- serverInfo: {
55
- name: this.serverTitle,
56
- version: this.serverVersion,
57
- },
58
- protocolVersion: "2025-11-25",
59
- instructions: this.serverInstructions,
60
- } as InitializeResult);
61
- }
62
- ping(params: PingParams): Promise<PingResult> {
63
- return Promise.resolve({} as PingResult);
64
- }
65
- "tools/list"(params: ToolsListParams): Promise<ToolsListResult> {
66
- return Promise.resolve({
67
- tools: this.toolsets.flatMap((toolset: ToolSet): Tool[] => {
68
- return toolset.getTools().map((tool: ToolDefinitionGeneric): Tool => {
69
- return {
70
- name: tool.name,
71
- description: tool.description,
72
- inputSchema: InputSchemaFromParameters(
73
- tool.params,
74
- )
75
- }
76
- });
77
- })
78
- } as ToolsListResult);
79
- }
80
-
81
- getTool(toolName: string): ToolDefinitionGeneric | null {
82
- for (const toolset of this.toolsets) {
83
- const maybeTool = toolset.getTool(toolName);
84
- if (maybeTool) return maybeTool;
85
- }
86
- return null;
87
- }
88
-
89
- async "tools/call"(params: ToolsCallParams): Promise<ToolsCallResult> {
90
- const toolName: string = params.name;
91
- const toolArgs: any = params.arguments || {};
92
- const tool = this.getTool(toolName);
93
- if (!tool) throw new MCPError("Tool not found!", { toolName: toolName });
94
- const referenceInstance = this.getReferenceInstanceForToolCall(toolName);
95
- const result: CallToolResult = await tool.callback(
96
- referenceInstance,
97
- ...ToolArgsToParameterList(toolArgs, tool.params),
98
- {},
99
- );
100
- return ((result: CallToolResult) => {toolArgs
101
- return {
102
- toolUseId: "",
103
- content: result.content,
104
- type: "tool_result",
105
- isError: result.isError || false,
106
- } as ToolsCallResult;
107
- })(result);
108
- }
109
- "prompts/list"(params: PromptsListParams): Promise<PromptsListResult> {
110
- throw new Error("Method not implemented.");
111
- }
112
- "prompts/get"(params: PromptsGetParams): Promise<PromptsGetResult> {
113
- throw new Error("Method not implemented.");
114
- }
115
- "resources/list"(params: ResourcesListParams): Promise<ResourcesListResult> {
116
- throw new Error("Method not implemented.");
117
- }
118
- "resources/read"(params: ResourcesReadParams): Promise<ResourcesReadResult> {
119
- throw new Error("Method not implemented.");
120
- }
121
- "resources/subscribe"(
122
- params: ResourcesSubscribeParams,
123
- ): Promise<ResourcesSubscribeResult> {
124
- throw new Error("Method not implemented.");
125
- }
126
- "resources/templates/list"(
127
- params: ResourcesTemplatesListParams,
128
- ): Promise<ResourcesTemplatesListResult> {
129
- throw new Error("Method not implemented.");
130
- }
131
- }
package/src/types.ts DELETED
@@ -1,165 +0,0 @@
1
- export * from "elysia-jsonrpc/mcp";
2
- export * from "elysia-jsonrpc/rpc";
3
- export * from "elysia-jsonrpc";
4
-
5
- export type ParamType =
6
- | "string"
7
- | "number"
8
- | "boolean"
9
- | "object"
10
- | "Record<string, any>";
11
-
12
- export type ParamDef = {
13
- type: ParamType;
14
- required?: boolean;
15
- default?: ParamDef["type"];
16
- name: string;
17
- description: string;
18
- };
19
-
20
- export type ExtractType<T extends ParamDef["type"]> = T extends "string"
21
- ? string
22
- : T extends "number"
23
- ? number
24
- : T extends "boolean"
25
- ? boolean
26
- : T extends "object"
27
- ? object
28
- : T extends "Record<string, any>"
29
- ? Record<string, any>
30
- : never;
31
-
32
- export type MethodResult<
33
- TResult,
34
- TParams extends readonly ParamDef[],
35
- TExtra = any,
36
- > = {
37
- name: string;
38
- params: TParams;
39
- callback: (...args: [...ParamsToTuple<TParams>, TExtra]) => Promise<TResult>;
40
- };
41
-
42
- export type NamedParam<N extends string, T extends ParamDef> = T & { name: N };
43
-
44
- export type ParamsToTuple<T extends readonly ParamDef[]> = {
45
- [K in keyof T]: T[K] extends ParamDef ? ExtractType<T[K]["type"]> : never;
46
- };
47
- export type NamedParamsToTuple<
48
- T extends readonly NamedParam<string, ParamDef>[],
49
- > = {
50
- [K in keyof T]: T[K] extends NamedParam<string, infer P>
51
- ? P extends ParamDef
52
- ? ExtractType<P["type"]>
53
- : never
54
- : never;
55
- };
56
-
57
- export interface PropertyDescription {
58
- type: string;
59
- description: string;
60
- }
61
-
62
- export interface InputSchemaType {
63
- [x: string]: unknown;
64
- type: "object";
65
- properties?: Record<string, PropertyDescription>;
66
- required?: string[];
67
- }
68
-
69
- export type ToolParameterDefinition = {
70
- required: boolean;
71
- type: string;
72
- description: string;
73
- default?: object;
74
- };
75
-
76
- export interface CallToolResult {
77
- content: any[];
78
- isError?: boolean;
79
- }
80
-
81
- export function InputSchemaFromParameters(
82
- params: ToolParameterDefinition[],
83
- ): InputSchemaType {
84
- return {
85
- type: "object",
86
- properties: Object.fromEntries(
87
- Object.entries(params).map(([k, v]) => [
88
- k,
89
- {
90
- description: v.description,
91
- type: v.type,
92
- default: v.default,
93
- } as PropertyDescription,
94
- ]),
95
- ) as Record<string, PropertyDescription>,
96
- required: Object.entries(params)
97
- .map(([k, v]) => (v.required ? k : undefined))
98
- .filter((v): v is string => v !== undefined),
99
- } as InputSchemaType;
100
- }
101
- export function ToolArgsToParameterList<
102
- TParams extends readonly NamedParam<string, ParamDef>[] = [],
103
- >(toolArgs: Record<string, any>, params: TParams): (any | undefined)[] {
104
- let res: any[] = [];
105
- for (const param of params) {
106
- res.push(toolArgs[param.name] ?? param.default ?? undefined);
107
- }
108
- return res;
109
- }
110
-
111
- export type ToolDefinitionCreator<
112
- ReferencedServerType,
113
- TParams extends readonly NamedParam<string, ParamDef>[] = [],
114
- > = <UserRef extends ReferencedServerType, TExtra = any>(
115
- callback: (
116
- ...args: [UserRef, ...NamedParamsToTuple<TParams>, TExtra?]
117
- ) => Promise<CallToolResult>,
118
- ) => any;
119
-
120
- export function ToolDefinition<
121
- ReferencedServerType = any,
122
- TParams extends readonly NamedParam<string, ParamDef>[] = [],
123
- >(methodName: string, methodDescription: string, params: TParams): ToolDefinitionCreator<ReferencedServerType, TParams> {
124
- return <UserRef extends ReferencedServerType, TExtra = any>(
125
- callback: (
126
- ...args: [UserRef, ...NamedParamsToTuple<TParams>, TExtra?]
127
- ) => Promise<CallToolResult>,
128
- ) => {
129
- return {
130
- name: methodName,
131
- description: methodDescription,
132
- params,
133
- callback,
134
- };
135
- };
136
- }
137
-
138
- export type ToolDefinitionGeneric<
139
- ReferencedServerType = any,
140
- // TParams extends readonly NamedParam<string, ParamDef>[] = [],
141
- TParams = any,
142
- > = {
143
- name: string;
144
- description: string;
145
- params: TParams;
146
- callback: (
147
- argfirst: ReferencedServerType,
148
- ...args: any[]
149
- ) => Promise<CallToolResult>;
150
- };
151
-
152
- export interface ToolResult {
153
- content: PropertyDescription[]; // TODO: maybe change
154
- }
155
-
156
- export interface Tool {
157
- name: string;
158
- description: string;
159
- inputSchema: InputSchemaType;
160
- }
161
-
162
- export interface ToolSet {
163
- getTools(): ToolDefinitionGeneric[];
164
- getTool(toolName: string): ToolDefinitionGeneric | undefined;
165
- }
package/test/mcp.test.ts DELETED
@@ -1,65 +0,0 @@
1
-
2
-
3
-
4
- import { describe, expect, it, test } from "bun:test";
5
- import { MCPServerService } from "../src";
6
- import { TestToolSet } from "./testToolSet";
7
-
8
- describe("MCPServerService", () => {
9
- const serverService = new MCPServerService(
10
- "testServerTitle",
11
- "1.0.0",
12
- "No instructions",
13
- (toolName: string) => { return serverService },
14
- [
15
- new TestToolSet(),
16
- ]
17
- );
18
-
19
- test("that the server has the tools", async () => {
20
- const toolsListResult = await serverService["tools/list"]({});
21
- expect(toolsListResult.tools).toBeArrayOfSize(2);
22
- expect(toolsListResult.tools.at(0)?.name).toBe("tool_name_1");
23
- expect(toolsListResult.tools.at(1)?.name).toBe("tool_name_2");
24
- });
25
-
26
- test("that the server has callable tools that works without args", async () => {
27
- const toolsListResult = await serverService["tools/call"]({
28
- name: "tool_name_1"
29
- });
30
- expect(toolsListResult.isError).toBeFalse();
31
- expect(toolsListResult.content).toBeArrayOfSize(1)
32
- expect(toolsListResult.content[0]).toBeObject();
33
- expect(toolsListResult.content[0]?.type).toBe("text");
34
- expect(toolsListResult.content[0]?.text).toBe(undefined);
35
- });
36
-
37
- test("that the server has callable tools that works with args", async () => {
38
- const toolsListResult = await serverService["tools/call"]({
39
- name: "tool_name_1",
40
- arguments: {
41
- "arg1": "any"
42
- }
43
- });
44
- expect(toolsListResult.isError).toBeFalse();
45
- expect(toolsListResult.content).toBeArrayOfSize(1)
46
- expect(toolsListResult.content[0]).toBeObject();
47
- expect(toolsListResult.content[0]?.type).toBe("text");
48
- expect(toolsListResult.content[0]?.text).toBe("any");
49
- });
50
-
51
- test("that the server has callable tools that works with arg name validation", async () => {
52
- const toolsListResult = await serverService["tools/call"]({
53
- name: "tool_name_1",
54
- arguments: {
55
- "arg0": "any"
56
- }
57
- });
58
- expect(toolsListResult.isError).toBeFalse();
59
- expect(toolsListResult.content).toBeArrayOfSize(1)
60
- expect(toolsListResult.content[0]).toBeObject();
61
- expect(toolsListResult.content[0]?.type).toBe("text");
62
- expect(toolsListResult.content[0]?.text).toBe(undefined); // since we dont have the correct argument name
63
- });
64
-
65
- });
@@ -1,67 +0,0 @@
1
- import {
2
- ToolDefinition,
3
- type CallToolResult,
4
- type ToolDefinitionGeneric,
5
- type ToolSet,
6
- } from "../src/types";
7
-
8
- export const Tool1 = ToolDefinition(
9
- "tool_name_1",
10
- "this tool is a test tool 1",
11
- [
12
- {
13
- name: "arg1",
14
- description: "describing arg1",
15
- type: "string",
16
- required: false,
17
- },
18
- ] as const,
19
- )(async (
20
- _reference: object,
21
- arg_string: string,
22
- _extra?: any,
23
- ): Promise<CallToolResult> => {
24
- return {
25
- content: [
26
- {
27
- type: "text",
28
- text: arg_string,
29
- },
30
- ],
31
- };
32
- });
33
-
34
- export const Tool2 = ToolDefinition(
35
- "tool_name_2",
36
- "this tool is a test tool 2",
37
- [
38
- {
39
- name: "arg1",
40
- description: "describing arg1",
41
- type: "string",
42
- required: false,
43
- },
44
- {
45
- name: "arg2",
46
- description: "describing arg2",
47
- type: "boolean",
48
- required: false,
49
- },
50
- ] as const,
51
- )(async (
52
- _reference: any,
53
- arg1: string,
54
- arg2: boolean,
55
- _extra?: any,
56
- ): Promise<CallToolResult> => {
57
- throw new Error("this always fails");
58
- });
59
-
60
- export class TestToolSet implements ToolSet {
61
- getTools(): ToolDefinitionGeneric[] {
62
- return [Tool1, Tool2];
63
- }
64
- getTool(toolName: string): ToolDefinitionGeneric | undefined {
65
- return this.getTools().find(x => x.name == toolName);
66
- }
67
- }
@@ -1,42 +0,0 @@
1
- import { describe, expect, it, test } from "bun:test";
2
- import { TestToolSet } from "./testToolSet";
3
-
4
-
5
- describe("TestToolSet", () => {
6
- const toolSet = new TestToolSet();
7
- test("that the toolset can handle any name", async () => {
8
- expect(toolSet.getTool("tool_name_1")).not.toBeUndefined();
9
- });
10
- const tools = toolSet.getTools();
11
- for (const toolId in tools) {
12
- const tool = tools[toolId];
13
- if (!tool) continue;
14
- test(`that the toolset ${toolId} is constructed correctly`, async () => {
15
- expect(tool.name).toBeOneOf(["tool_name_1", "tool_name_2"]);
16
- expect(tool.description).not.toBe("");
17
- expect(tool.params).toBeArray();
18
- expect(tool.params).not.toBeArrayOfSize(0);
19
- expect(tool.params.length).toBeOneOf([1, 2]);
20
- });
21
- // test(`that the toolset ${i} is callable with any amount of args`, async () => {
22
- const callback = tool.callback;
23
- const args = [this, "test", false, "test"];
24
-
25
- for (let n = 0; n < args.length + 1; n++) {
26
- test(`should handle ${n} args correctly `, async () => {
27
- const result = await expect(async () => {
28
- const xargs = args.slice(0, n);
29
- const ret = await (callback as any)(...xargs);
30
- expect(ret).toBeObject();
31
- expect(ret.content).toBeArrayOfSize(1);
32
- if(n > 1) expect(ret.content[0].text).toBe(args[1]);
33
- });
34
- if (toolId == "0") {
35
- result.not.toThrow();
36
- } else if (toolId == "1") {
37
- result.toThrow("this always fails");
38
- }
39
- });
40
- }
41
- }
42
- });
package/tsconfig.dts.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "declaration": true,
4
- "emitDeclarationOnly": true,
5
- "outDir": "./dist",
6
- "skipLibCheck": true,
7
- "esModuleInterop": true,
8
- "noCheck": true,
9
- },
10
- "exclude": ["node_modules", "test", "src/**/*.d.ts"],
11
- "include": ["src/**/*.ts"]
12
- }
package/tsconfig.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": false,
16
- "declaration": true,
17
- "declarationDir": "./out",
18
- "emitDeclarationOnly": true,
19
- "rootDir": "./src",
20
-
21
- // Best practices
22
- "strict": true,
23
- "skipLibCheck": true,
24
- "noFallthroughCasesInSwitch": true,
25
- "noUncheckedIndexedAccess": true,
26
- "noImplicitOverride": true,
27
-
28
- // Some stricter flags (disabled by default)
29
- "noUnusedLocals": false,
30
- "noUnusedParameters": false,
31
- "noPropertyAccessFromIndexSignature": false
32
- },
33
- "include": [
34
- "src/**/*.ts"
35
- ]
36
- }