Skip to content

Commit 2eee80d

Browse files
feat(core): hooks to the stdio transport layer
1 parent 4d99a74 commit 2eee80d

File tree

16 files changed

+1214
-7
lines changed

16 files changed

+1214
-7
lines changed

examples/mcp-sdk/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "example-mcp-sdk",
3+
"version": "0.0.1",
4+
"bin": "./dist/index.js",
5+
"scripts": {
6+
"dev": "tsx ./src/index.ts",
7+
"build": "pkgroll --watch"
8+
},
9+
"dependencies": {
10+
"@hono/node-server": "^1.13.8",
11+
"@modelcontextprotocol/sdk": "^1.7.0",
12+
"hono": "^4.7.4",
13+
"muppet": "workspace:*"
14+
},
15+
"devDependencies": {
16+
"pkgroll": "^2.11.2",
17+
"tsx": "^4.19.3",
18+
"typescript": "^5.8.2"
19+
}
20+
}

examples/mcp-sdk/src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2+
import { createMuppet } from "muppet";
3+
4+
const transport = new StdioServerTransport();
5+
6+
createMuppet({
7+
name: "mcp-sdk",
8+
version: "0.0.1",
9+
transport,
10+
logger: {
11+
path: "/Users/adityamathur/dev/muppet-dev/muppet/examples/mcp-sdk/dist/main.log",
12+
},
13+
}).then((muppet) => muppet.start());

examples/mcp-sdk/tsconfig.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"lib": ["ESNext", "DOM"],
5+
"moduleDetection": "force",
6+
"useDefineForClassFields": false,
7+
"experimentalDecorators": true,
8+
"module": "ESNext",
9+
"moduleResolution": "bundler",
10+
"resolveJsonModule": true,
11+
"allowJs": true,
12+
"strict": true,
13+
"noFallthroughCasesInSwitch": true,
14+
"noImplicitOverride": true,
15+
"noImplicitReturns": true,
16+
"noUnusedLocals": true,
17+
"noImplicitAny": true,
18+
"noUnusedParameters": true,
19+
"declaration": false,
20+
"noEmit": true,
21+
"outDir": "dist/",
22+
"sourceMap": true,
23+
"esModuleInterop": true,
24+
"forceConsistentCasingInFileNames": true,
25+
"isolatedModules": true,
26+
"verbatimModuleSyntax": true,
27+
"skipLibCheck": true
28+
},
29+
"include": ["./src/**/*"]
30+
}

packages/core/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@muppet/validations",
2+
"name": "muppet",
33
"version": "0.1.0",
44
"private": true,
55
"type": "module",
@@ -10,7 +10,7 @@
1010
"dist"
1111
],
1212
"scripts": {
13-
"build": "pkgroll --minify"
13+
"build": "pkgroll --watch"
1414
},
1515
"exports": {
1616
".": {
@@ -24,8 +24,12 @@
2424
}
2525
}
2626
},
27-
"peerDependencies": {},
27+
"peerDependencies": {
28+
"@modelcontextprotocol/sdk": "^1.7.0",
29+
"pino": "^9.6.0"
30+
},
2831
"devDependencies": {
32+
"@standard-schema/spec": "^1.0.0",
2933
"pkgroll": "^2.5.1"
3034
}
3135
}

packages/core/src/createMuppet.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2+
import { messageHandler } from "./handlers";
3+
import type { MuppetConfig } from "./types";
4+
import pino from "pino";
5+
6+
export function createMuppet(config: MuppetConfig) {
7+
const { transport, logger: loggerConfig } = config;
8+
9+
const logger = pino(
10+
loggerConfig ? pino.destination(loggerConfig.path) : { enabled: false },
11+
);
12+
13+
transport.onclose = () => {
14+
logger.info("Connection closed");
15+
};
16+
17+
transport.onerror = (err) => {
18+
logger.error({ err }, "Connection error");
19+
};
20+
21+
transport.onmessage = async (message) => {
22+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
23+
let payload: any;
24+
25+
try {
26+
const ctx = { logger };
27+
payload = await messageHandler(ctx, message, config);
28+
} catch (err) {
29+
logger.error({ err }, "Error processing message");
30+
payload = {
31+
error: {
32+
// @ts-expect-error
33+
code: Number.isSafeInteger(err.code)
34+
? // @ts-expect-error
35+
err.code
36+
: ErrorCode.InternalError,
37+
// @ts-expect-error
38+
message: err.message ?? "Internal error",
39+
},
40+
};
41+
}
42+
43+
transport.send({
44+
jsonrpc: "2.0",
45+
// @ts-expect-error
46+
id: _request.id,
47+
...payload,
48+
});
49+
};
50+
51+
return transport;
52+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./messageHandler";
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
InitializeRequestSchema,
3+
LATEST_PROTOCOL_VERSION,
4+
SUPPORTED_PROTOCOL_VERSIONS,
5+
} from "@modelcontextprotocol/sdk/types.js";
6+
import type { HandlerFn } from "../types";
7+
8+
export const initializeHandler: HandlerFn = (ctx, request, config) => {
9+
ctx.logger.info({
10+
msg: "Initializing mcp server",
11+
request,
12+
});
13+
14+
const validatedData = InitializeRequestSchema.parse(request);
15+
const requestedVersion = validatedData.params.protocolVersion;
16+
17+
return {
18+
result: {
19+
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
20+
? requestedVersion
21+
: LATEST_PROTOCOL_VERSION,
22+
serverInfo: {
23+
name: config.name,
24+
version: config.version,
25+
},
26+
capabilities: {
27+
tools: {},
28+
},
29+
},
30+
};
31+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
RequestSchema,
3+
type JSONRPCMessage,
4+
} from "@modelcontextprotocol/sdk/types.js";
5+
import type { HandlerFn } from "../types";
6+
import { initializeHandler } from "./initializeHandler";
7+
import { methodNotFoundHandler } from "./notFound";
8+
9+
const handlersMap: Record<string, HandlerFn | undefined> = {
10+
initialize: initializeHandler,
11+
};
12+
13+
export const messageHandler: HandlerFn = async (ctx, request, config) => {
14+
const _request = RequestSchema.parse(request);
15+
16+
const handler = handlersMap[_request.method] ?? methodNotFoundHandler;
17+
18+
const response = await handler(ctx, request, config);
19+
20+
ctx.logger.info({
21+
msg: `Response for ${_request.method}`,
22+
response,
23+
});
24+
25+
return response;
26+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2+
import type { HandlerFn } from "../types";
3+
4+
export const methodNotFoundHandler: HandlerFn = (ctx, request) => {
5+
ctx.logger.error({
6+
msg: "Method not found",
7+
request,
8+
});
9+
10+
return {
11+
error: {
12+
code: ErrorCode.MethodNotFound,
13+
message: "Method not found",
14+
},
15+
};
16+
};

packages/core/src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export function defineConfig() {
2-
return {};
3-
}
1+
export * from "./createMuppet";
2+
export * from "./types";

0 commit comments

Comments
 (0)