@contractspec/bundle.library 2.4.0 → 2.6.0
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/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +51 -0
- package/dist/application/index.js +138 -49
- package/dist/application/mcp/cliMcp.d.ts +3 -56
- package/dist/application/mcp/cliMcp.js +138 -49
- package/dist/application/mcp/common.d.ts +5 -57
- package/dist/application/mcp/common.js +138 -49
- package/dist/application/mcp/docsMcp.d.ts +3 -56
- package/dist/application/mcp/docsMcp.js +138 -49
- package/dist/application/mcp/index.js +138 -49
- package/dist/application/mcp/internalMcp.d.ts +3 -56
- package/dist/application/mcp/internalMcp.js +138 -49
- package/dist/node/application/index.js +138 -49
- package/dist/node/application/mcp/cliMcp.js +138 -49
- package/dist/node/application/mcp/common.js +138 -49
- package/dist/node/application/mcp/docsMcp.js +138 -49
- package/dist/node/application/mcp/index.js +138 -49
- package/dist/node/application/mcp/internalMcp.js +138 -49
- package/package.json +17 -17
- package/src/application/mcp/common.ts +180 -87
package/.turbo/turbo-build.log
CHANGED
|
@@ -3,9 +3,9 @@ $ bun run prebuild && bun run build:bundle && bun run build:types
|
|
|
3
3
|
$ contractspec-bun-build prebuild
|
|
4
4
|
$ contractspec-bun-build transpile
|
|
5
5
|
[contractspec-bun-build] transpile target=bun root=src entries=281
|
|
6
|
-
Bundled 281 modules in
|
|
6
|
+
Bundled 281 modules in 125ms
|
|
7
7
|
|
|
8
|
-
application/index.js
|
|
8
|
+
application/index.js 23.85 KB (entry point)
|
|
9
9
|
presentation/features/templates/types.js 8 bytes (entry point)
|
|
10
10
|
presentation/features/organisms/index.js 49.89 KB (entry point)
|
|
11
11
|
presentation/features/molecules/index.js 18.78 KB (entry point)
|
|
@@ -276,21 +276,21 @@ Bundled 281 modules in 135ms
|
|
|
276
276
|
components/docs/advanced/AdvancedWorkflowMonitoringPage.js 4.41 KB (entry point)
|
|
277
277
|
components/docs/architecture/ArchitectureAppConfigPage.js 14.0 KB (entry point)
|
|
278
278
|
components/docs/architecture/ArchitectureIntegrationBindingPage.js 14.57 KB (entry point)
|
|
279
|
-
application/mcp/index.js
|
|
280
|
-
application/mcp/cliMcp.js
|
|
281
|
-
application/mcp/docsMcp.js
|
|
279
|
+
application/mcp/index.js 23.85 KB (entry point)
|
|
280
|
+
application/mcp/cliMcp.js 12.48 KB (entry point)
|
|
281
|
+
application/mcp/docsMcp.js 10.13 KB (entry point)
|
|
282
282
|
features/docs/index.js 443 bytes (entry point)
|
|
283
283
|
features/docs/docs.contracts.js 443 bytes (entry point)
|
|
284
|
-
application/mcp/internalMcp.js
|
|
284
|
+
application/mcp/internalMcp.js 11.17 KB (entry point)
|
|
285
285
|
infrastructure/elysia/logger.js 0.78 KB (entry point)
|
|
286
|
-
application/mcp/common.js
|
|
286
|
+
application/mcp/common.js 4.49 KB (entry point)
|
|
287
287
|
components/docs/DocsIndexPage.js 12.24 KB (entry point)
|
|
288
288
|
components/docs/advanced/AdvancedMCPPage.js 15.27 KB (entry point)
|
|
289
289
|
|
|
290
290
|
[contractspec-bun-build] transpile target=node root=src entries=281
|
|
291
|
-
Bundled 281 modules in
|
|
291
|
+
Bundled 281 modules in 94ms
|
|
292
292
|
|
|
293
|
-
application/index.js
|
|
293
|
+
application/index.js 23.85 KB (entry point)
|
|
294
294
|
presentation/features/templates/types.js 0 KB (entry point)
|
|
295
295
|
presentation/features/organisms/index.js 49.89 KB (entry point)
|
|
296
296
|
presentation/features/molecules/index.js 18.77 KB (entry point)
|
|
@@ -561,14 +561,14 @@ Bundled 281 modules in 116ms
|
|
|
561
561
|
components/docs/advanced/AdvancedWorkflowMonitoringPage.js 4.40 KB (entry point)
|
|
562
562
|
components/docs/architecture/ArchitectureAppConfigPage.js 14.0 KB (entry point)
|
|
563
563
|
components/docs/architecture/ArchitectureIntegrationBindingPage.js 14.56 KB (entry point)
|
|
564
|
-
application/mcp/index.js
|
|
565
|
-
application/mcp/cliMcp.js
|
|
566
|
-
application/mcp/docsMcp.js
|
|
564
|
+
application/mcp/index.js 23.85 KB (entry point)
|
|
565
|
+
application/mcp/cliMcp.js 12.49 KB (entry point)
|
|
566
|
+
application/mcp/docsMcp.js 10.13 KB (entry point)
|
|
567
567
|
features/docs/index.js 435 bytes (entry point)
|
|
568
568
|
features/docs/docs.contracts.js 435 bytes (entry point)
|
|
569
|
-
application/mcp/internalMcp.js
|
|
569
|
+
application/mcp/internalMcp.js 11.16 KB (entry point)
|
|
570
570
|
infrastructure/elysia/logger.js 0.77 KB (entry point)
|
|
571
|
-
application/mcp/common.js
|
|
571
|
+
application/mcp/common.js 4.48 KB (entry point)
|
|
572
572
|
components/docs/DocsIndexPage.js 12.23 KB (entry point)
|
|
573
573
|
components/docs/advanced/AdvancedMCPPage.js 15.26 KB (entry point)
|
|
574
574
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @contractspec/bundle.library
|
|
2
2
|
|
|
3
|
+
## 2.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bae3db1: fix: build issues
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [bae3db1]
|
|
12
|
+
- @contractspec/lib.contracts-integrations@2.6.0
|
|
13
|
+
- @contractspec/lib.contracts-library@2.6.0
|
|
14
|
+
- @contractspec/lib.contracts-runtime-server-graphql@2.6.0
|
|
15
|
+
- @contractspec/lib.contracts-runtime-server-mcp@2.6.0
|
|
16
|
+
- @contractspec/lib.contracts-runtime-server-rest@2.6.0
|
|
17
|
+
- @contractspec/lib.contracts-spec@2.6.0
|
|
18
|
+
- @contractspec/lib.design-system@2.6.0
|
|
19
|
+
- @contractspec/lib.example-shared-ui@2.6.0
|
|
20
|
+
- @contractspec/lib.logger@2.6.0
|
|
21
|
+
- @contractspec/lib.runtime-sandbox@1.6.0
|
|
22
|
+
- @contractspec/lib.schema@2.6.0
|
|
23
|
+
- @contractspec/lib.ui-kit-web@2.6.0
|
|
24
|
+
- @contractspec/lib.ui-link@2.6.0
|
|
25
|
+
- @contractspec/module.examples@2.6.0
|
|
26
|
+
|
|
27
|
+
## 2.5.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- c83c323: feat: major change to content generation
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [4fa3bd4]
|
|
36
|
+
- Updated dependencies [63eee9b]
|
|
37
|
+
- Updated dependencies [284cbe2]
|
|
38
|
+
- Updated dependencies [c83c323]
|
|
39
|
+
- @contractspec/lib.contracts-spec@2.5.0
|
|
40
|
+
- @contractspec/lib.contracts-integrations@2.5.0
|
|
41
|
+
- @contractspec/lib.contracts-runtime-server-graphql@2.5.0
|
|
42
|
+
- @contractspec/lib.contracts-runtime-server-rest@2.5.0
|
|
43
|
+
- @contractspec/lib.contracts-runtime-server-mcp@2.5.0
|
|
44
|
+
- @contractspec/lib.contracts-library@2.5.0
|
|
45
|
+
- @contractspec/lib.example-shared-ui@2.5.0
|
|
46
|
+
- @contractspec/lib.runtime-sandbox@1.5.0
|
|
47
|
+
- @contractspec/lib.design-system@2.5.0
|
|
48
|
+
- @contractspec/module.examples@2.5.0
|
|
49
|
+
- @contractspec/lib.ui-kit-web@2.5.0
|
|
50
|
+
- @contractspec/lib.ui-link@2.5.0
|
|
51
|
+
- @contractspec/lib.logger@2.5.0
|
|
52
|
+
- @contractspec/lib.schema@2.5.0
|
|
53
|
+
|
|
3
54
|
## 2.4.0
|
|
4
55
|
|
|
5
56
|
### Minor Changes
|
|
@@ -2,72 +2,161 @@
|
|
|
2
2
|
// src/application/mcp/common.ts
|
|
3
3
|
import { PresentationRegistry } from "@contractspec/lib.contracts-spec/presentations";
|
|
4
4
|
import { createMcpServer } from "@contractspec/lib.contracts-runtime-server-mcp/provider-mcp";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
log: (...args) => {
|
|
11
|
-
if (!isDebug)
|
|
12
|
-
return;
|
|
13
|
-
logger.info(toMessage(args));
|
|
14
|
-
},
|
|
15
|
-
info: (...args) => {
|
|
16
|
-
if (!isDebug)
|
|
17
|
-
return;
|
|
18
|
-
logger.info(toMessage(args));
|
|
19
|
-
},
|
|
20
|
-
warn: (...args) => {
|
|
21
|
-
logger.warn(toMessage(args));
|
|
22
|
-
},
|
|
23
|
-
error: (...args) => {
|
|
24
|
-
logger.error(toMessage(args));
|
|
25
|
-
},
|
|
26
|
-
debug: (...args) => {
|
|
27
|
-
if (!isDebug)
|
|
28
|
-
return;
|
|
29
|
-
logger.debug(toMessage(args));
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
7
|
+
import { Elysia } from "elysia";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
33
9
|
var baseCtx = {
|
|
34
10
|
actor: "anonymous",
|
|
35
11
|
decide: async () => ({ effect: "allow" })
|
|
36
12
|
};
|
|
37
|
-
function
|
|
13
|
+
function createJsonRpcErrorResponse(status, code, message, data) {
|
|
14
|
+
return new Response(JSON.stringify({
|
|
15
|
+
jsonrpc: "2.0",
|
|
16
|
+
error: {
|
|
17
|
+
code,
|
|
18
|
+
message,
|
|
19
|
+
...data ? { data } : {}
|
|
20
|
+
},
|
|
21
|
+
id: null
|
|
22
|
+
}), {
|
|
23
|
+
status,
|
|
24
|
+
headers: {
|
|
25
|
+
"content-type": "application/json"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function createSessionState({
|
|
38
30
|
logger,
|
|
39
|
-
path,
|
|
40
31
|
serverName,
|
|
41
32
|
ops,
|
|
42
33
|
resources,
|
|
43
34
|
prompts,
|
|
44
|
-
presentations
|
|
35
|
+
presentations,
|
|
36
|
+
stateful
|
|
45
37
|
}) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
serverInfo: {
|
|
51
|
-
name: serverName,
|
|
52
|
-
version: "1.0.0"
|
|
53
|
-
},
|
|
54
|
-
stateless: process.env.CONTRACTSPEC_MCP_STATEFUL !== "1",
|
|
55
|
-
enableJsonResponse: true,
|
|
38
|
+
const server = new McpServer({
|
|
39
|
+
name: serverName,
|
|
40
|
+
version: "1.0.0"
|
|
41
|
+
}, {
|
|
56
42
|
capabilities: {
|
|
57
43
|
tools: {},
|
|
58
44
|
resources: {},
|
|
59
45
|
prompts: {},
|
|
60
46
|
logging: {}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
logger.info("Setting up MCP server...");
|
|
50
|
+
createMcpServer(server, ops, resources, prompts, {
|
|
51
|
+
logger,
|
|
52
|
+
toolCtx: () => baseCtx,
|
|
53
|
+
promptCtx: () => ({ locale: "en" }),
|
|
54
|
+
resourceCtx: () => ({ locale: "en" }),
|
|
55
|
+
presentations: new PresentationRegistry(presentations)
|
|
56
|
+
});
|
|
57
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
58
|
+
sessionIdGenerator: stateful ? () => randomUUID() : undefined,
|
|
59
|
+
enableJsonResponse: true
|
|
60
|
+
});
|
|
61
|
+
return server.connect(transport).then(() => ({ server, transport }));
|
|
62
|
+
}
|
|
63
|
+
async function closeSessionState(state) {
|
|
64
|
+
await Promise.allSettled([state.transport.close(), state.server.close()]);
|
|
65
|
+
}
|
|
66
|
+
function toErrorMessage(error) {
|
|
67
|
+
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
68
|
+
}
|
|
69
|
+
function createMcpElysiaHandler({
|
|
70
|
+
logger,
|
|
71
|
+
path,
|
|
72
|
+
serverName,
|
|
73
|
+
ops,
|
|
74
|
+
resources,
|
|
75
|
+
prompts,
|
|
76
|
+
presentations
|
|
77
|
+
}) {
|
|
78
|
+
logger.info("Setting up MCP handler...");
|
|
79
|
+
const isStateful = process.env.CONTRACTSPEC_MCP_STATEFUL === "1";
|
|
80
|
+
const sessions = new Map;
|
|
81
|
+
async function handleStateless(request) {
|
|
82
|
+
const state = await createSessionState({
|
|
83
|
+
logger,
|
|
84
|
+
path,
|
|
85
|
+
serverName,
|
|
86
|
+
ops,
|
|
87
|
+
resources,
|
|
88
|
+
prompts,
|
|
89
|
+
presentations,
|
|
90
|
+
stateful: false
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
return await state.transport.handleRequest(request);
|
|
94
|
+
} finally {
|
|
95
|
+
await closeSessionState(state);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function closeSession(sessionId) {
|
|
99
|
+
const state = sessions.get(sessionId);
|
|
100
|
+
if (!state)
|
|
101
|
+
return;
|
|
102
|
+
sessions.delete(sessionId);
|
|
103
|
+
await closeSessionState(state);
|
|
104
|
+
}
|
|
105
|
+
async function handleStateful(request) {
|
|
106
|
+
const requestedSessionId = request.headers.get("mcp-session-id");
|
|
107
|
+
let state;
|
|
108
|
+
let createdState = false;
|
|
109
|
+
if (requestedSessionId) {
|
|
110
|
+
const existing = sessions.get(requestedSessionId);
|
|
111
|
+
if (!existing) {
|
|
112
|
+
return createJsonRpcErrorResponse(404, -32001, "Session not found");
|
|
113
|
+
}
|
|
114
|
+
state = existing;
|
|
115
|
+
} else {
|
|
116
|
+
state = await createSessionState({
|
|
65
117
|
logger,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
path,
|
|
119
|
+
serverName,
|
|
120
|
+
ops,
|
|
121
|
+
resources,
|
|
122
|
+
prompts,
|
|
123
|
+
presentations,
|
|
124
|
+
stateful: true
|
|
125
|
+
});
|
|
126
|
+
createdState = true;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const response = await state.transport.handleRequest(request);
|
|
130
|
+
const activeSessionId = state.transport.sessionId;
|
|
131
|
+
if (activeSessionId && !sessions.has(activeSessionId)) {
|
|
132
|
+
sessions.set(activeSessionId, state);
|
|
133
|
+
}
|
|
134
|
+
if (request.method === "DELETE" && activeSessionId) {
|
|
135
|
+
await closeSession(activeSessionId);
|
|
136
|
+
} else if (!activeSessionId && createdState) {
|
|
137
|
+
await closeSessionState(state);
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (createdState) {
|
|
142
|
+
await closeSessionState(state);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return new Elysia({ name: `mcp-${serverName}` }).all(path, async ({ request }) => {
|
|
148
|
+
try {
|
|
149
|
+
if (isStateful) {
|
|
150
|
+
return await handleStateful(request);
|
|
151
|
+
}
|
|
152
|
+
return await handleStateless(request);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error("Error handling MCP request", {
|
|
155
|
+
path,
|
|
156
|
+
method: request.method,
|
|
157
|
+
error: toErrorMessage(error)
|
|
70
158
|
});
|
|
159
|
+
return createJsonRpcErrorResponse(500, -32000, "Internal error");
|
|
71
160
|
}
|
|
72
161
|
});
|
|
73
162
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export declare function createCliMcpHandler(path?: string): import("elysia").default<"", {
|
|
2
2
|
decorator: {};
|
|
3
|
-
store: {
|
|
4
|
-
authInfo: AuthInfo | undefined;
|
|
5
|
-
};
|
|
3
|
+
store: {};
|
|
6
4
|
derive: {};
|
|
7
5
|
resolve: {};
|
|
8
6
|
}, {
|
|
@@ -16,39 +14,6 @@ export declare function createCliMcpHandler(path?: string): import("elysia").def
|
|
|
16
14
|
parser: {};
|
|
17
15
|
response: {};
|
|
18
16
|
}, {
|
|
19
|
-
[x: string]: {
|
|
20
|
-
"*": {
|
|
21
|
-
[x: string]: {
|
|
22
|
-
body: unknown;
|
|
23
|
-
params: {
|
|
24
|
-
"*": string;
|
|
25
|
-
} & {};
|
|
26
|
-
query: unknown;
|
|
27
|
-
headers: unknown;
|
|
28
|
-
response: {
|
|
29
|
-
200: {} | {
|
|
30
|
-
jsonrpc: string;
|
|
31
|
-
error: {
|
|
32
|
-
code: import("@modelcontextprotocol/sdk/types.js").ErrorCode;
|
|
33
|
-
message: string;
|
|
34
|
-
data: string;
|
|
35
|
-
};
|
|
36
|
-
id: null;
|
|
37
|
-
};
|
|
38
|
-
422: {
|
|
39
|
-
type: "validation";
|
|
40
|
-
on: string;
|
|
41
|
-
summary?: string;
|
|
42
|
-
message?: string;
|
|
43
|
-
found?: unknown;
|
|
44
|
-
property?: string;
|
|
45
|
-
expected?: string;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
} & {
|
|
52
17
|
[x: string]: {
|
|
53
18
|
[x: string]: {
|
|
54
19
|
body: unknown;
|
|
@@ -56,15 +21,7 @@ export declare function createCliMcpHandler(path?: string): import("elysia").def
|
|
|
56
21
|
query: unknown;
|
|
57
22
|
headers: unknown;
|
|
58
23
|
response: {
|
|
59
|
-
200:
|
|
60
|
-
jsonrpc: string;
|
|
61
|
-
error: {
|
|
62
|
-
code: import("@modelcontextprotocol/sdk/types.js").ErrorCode;
|
|
63
|
-
message: string;
|
|
64
|
-
data: string;
|
|
65
|
-
};
|
|
66
|
-
id: null;
|
|
67
|
-
};
|
|
24
|
+
200: Response;
|
|
68
25
|
};
|
|
69
26
|
};
|
|
70
27
|
};
|
|
@@ -79,15 +36,5 @@ export declare function createCliMcpHandler(path?: string): import("elysia").def
|
|
|
79
36
|
resolve: {};
|
|
80
37
|
schema: {};
|
|
81
38
|
standaloneSchema: {};
|
|
82
|
-
response: {
|
|
83
|
-
200: {} | {
|
|
84
|
-
jsonrpc: string;
|
|
85
|
-
error: {
|
|
86
|
-
code: import("@modelcontextprotocol/sdk/types.js").ErrorCode;
|
|
87
|
-
message: string;
|
|
88
|
-
data: string;
|
|
89
|
-
};
|
|
90
|
-
id: null;
|
|
91
|
-
};
|
|
92
|
-
};
|
|
39
|
+
response: {};
|
|
93
40
|
}>;
|
|
@@ -2,72 +2,161 @@
|
|
|
2
2
|
// src/application/mcp/common.ts
|
|
3
3
|
import { PresentationRegistry } from "@contractspec/lib.contracts-spec/presentations";
|
|
4
4
|
import { createMcpServer } from "@contractspec/lib.contracts-runtime-server-mcp/provider-mcp";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
log: (...args) => {
|
|
11
|
-
if (!isDebug)
|
|
12
|
-
return;
|
|
13
|
-
logger.info(toMessage(args));
|
|
14
|
-
},
|
|
15
|
-
info: (...args) => {
|
|
16
|
-
if (!isDebug)
|
|
17
|
-
return;
|
|
18
|
-
logger.info(toMessage(args));
|
|
19
|
-
},
|
|
20
|
-
warn: (...args) => {
|
|
21
|
-
logger.warn(toMessage(args));
|
|
22
|
-
},
|
|
23
|
-
error: (...args) => {
|
|
24
|
-
logger.error(toMessage(args));
|
|
25
|
-
},
|
|
26
|
-
debug: (...args) => {
|
|
27
|
-
if (!isDebug)
|
|
28
|
-
return;
|
|
29
|
-
logger.debug(toMessage(args));
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
7
|
+
import { Elysia } from "elysia";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
33
9
|
var baseCtx = {
|
|
34
10
|
actor: "anonymous",
|
|
35
11
|
decide: async () => ({ effect: "allow" })
|
|
36
12
|
};
|
|
37
|
-
function
|
|
13
|
+
function createJsonRpcErrorResponse(status, code, message, data) {
|
|
14
|
+
return new Response(JSON.stringify({
|
|
15
|
+
jsonrpc: "2.0",
|
|
16
|
+
error: {
|
|
17
|
+
code,
|
|
18
|
+
message,
|
|
19
|
+
...data ? { data } : {}
|
|
20
|
+
},
|
|
21
|
+
id: null
|
|
22
|
+
}), {
|
|
23
|
+
status,
|
|
24
|
+
headers: {
|
|
25
|
+
"content-type": "application/json"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function createSessionState({
|
|
38
30
|
logger,
|
|
39
|
-
path,
|
|
40
31
|
serverName,
|
|
41
32
|
ops,
|
|
42
33
|
resources,
|
|
43
34
|
prompts,
|
|
44
|
-
presentations
|
|
35
|
+
presentations,
|
|
36
|
+
stateful
|
|
45
37
|
}) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
serverInfo: {
|
|
51
|
-
name: serverName,
|
|
52
|
-
version: "1.0.0"
|
|
53
|
-
},
|
|
54
|
-
stateless: process.env.CONTRACTSPEC_MCP_STATEFUL !== "1",
|
|
55
|
-
enableJsonResponse: true,
|
|
38
|
+
const server = new McpServer({
|
|
39
|
+
name: serverName,
|
|
40
|
+
version: "1.0.0"
|
|
41
|
+
}, {
|
|
56
42
|
capabilities: {
|
|
57
43
|
tools: {},
|
|
58
44
|
resources: {},
|
|
59
45
|
prompts: {},
|
|
60
46
|
logging: {}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
logger.info("Setting up MCP server...");
|
|
50
|
+
createMcpServer(server, ops, resources, prompts, {
|
|
51
|
+
logger,
|
|
52
|
+
toolCtx: () => baseCtx,
|
|
53
|
+
promptCtx: () => ({ locale: "en" }),
|
|
54
|
+
resourceCtx: () => ({ locale: "en" }),
|
|
55
|
+
presentations: new PresentationRegistry(presentations)
|
|
56
|
+
});
|
|
57
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
58
|
+
sessionIdGenerator: stateful ? () => randomUUID() : undefined,
|
|
59
|
+
enableJsonResponse: true
|
|
60
|
+
});
|
|
61
|
+
return server.connect(transport).then(() => ({ server, transport }));
|
|
62
|
+
}
|
|
63
|
+
async function closeSessionState(state) {
|
|
64
|
+
await Promise.allSettled([state.transport.close(), state.server.close()]);
|
|
65
|
+
}
|
|
66
|
+
function toErrorMessage(error) {
|
|
67
|
+
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
68
|
+
}
|
|
69
|
+
function createMcpElysiaHandler({
|
|
70
|
+
logger,
|
|
71
|
+
path,
|
|
72
|
+
serverName,
|
|
73
|
+
ops,
|
|
74
|
+
resources,
|
|
75
|
+
prompts,
|
|
76
|
+
presentations
|
|
77
|
+
}) {
|
|
78
|
+
logger.info("Setting up MCP handler...");
|
|
79
|
+
const isStateful = process.env.CONTRACTSPEC_MCP_STATEFUL === "1";
|
|
80
|
+
const sessions = new Map;
|
|
81
|
+
async function handleStateless(request) {
|
|
82
|
+
const state = await createSessionState({
|
|
83
|
+
logger,
|
|
84
|
+
path,
|
|
85
|
+
serverName,
|
|
86
|
+
ops,
|
|
87
|
+
resources,
|
|
88
|
+
prompts,
|
|
89
|
+
presentations,
|
|
90
|
+
stateful: false
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
return await state.transport.handleRequest(request);
|
|
94
|
+
} finally {
|
|
95
|
+
await closeSessionState(state);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function closeSession(sessionId) {
|
|
99
|
+
const state = sessions.get(sessionId);
|
|
100
|
+
if (!state)
|
|
101
|
+
return;
|
|
102
|
+
sessions.delete(sessionId);
|
|
103
|
+
await closeSessionState(state);
|
|
104
|
+
}
|
|
105
|
+
async function handleStateful(request) {
|
|
106
|
+
const requestedSessionId = request.headers.get("mcp-session-id");
|
|
107
|
+
let state;
|
|
108
|
+
let createdState = false;
|
|
109
|
+
if (requestedSessionId) {
|
|
110
|
+
const existing = sessions.get(requestedSessionId);
|
|
111
|
+
if (!existing) {
|
|
112
|
+
return createJsonRpcErrorResponse(404, -32001, "Session not found");
|
|
113
|
+
}
|
|
114
|
+
state = existing;
|
|
115
|
+
} else {
|
|
116
|
+
state = await createSessionState({
|
|
65
117
|
logger,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
path,
|
|
119
|
+
serverName,
|
|
120
|
+
ops,
|
|
121
|
+
resources,
|
|
122
|
+
prompts,
|
|
123
|
+
presentations,
|
|
124
|
+
stateful: true
|
|
125
|
+
});
|
|
126
|
+
createdState = true;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const response = await state.transport.handleRequest(request);
|
|
130
|
+
const activeSessionId = state.transport.sessionId;
|
|
131
|
+
if (activeSessionId && !sessions.has(activeSessionId)) {
|
|
132
|
+
sessions.set(activeSessionId, state);
|
|
133
|
+
}
|
|
134
|
+
if (request.method === "DELETE" && activeSessionId) {
|
|
135
|
+
await closeSession(activeSessionId);
|
|
136
|
+
} else if (!activeSessionId && createdState) {
|
|
137
|
+
await closeSessionState(state);
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (createdState) {
|
|
142
|
+
await closeSessionState(state);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return new Elysia({ name: `mcp-${serverName}` }).all(path, async ({ request }) => {
|
|
148
|
+
try {
|
|
149
|
+
if (isStateful) {
|
|
150
|
+
return await handleStateful(request);
|
|
151
|
+
}
|
|
152
|
+
return await handleStateless(request);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error("Error handling MCP request", {
|
|
155
|
+
path,
|
|
156
|
+
method: request.method,
|
|
157
|
+
error: toErrorMessage(error)
|
|
70
158
|
});
|
|
159
|
+
return createJsonRpcErrorResponse(500, -32000, "Internal error");
|
|
71
160
|
}
|
|
72
161
|
});
|
|
73
162
|
}
|