@contractspec/lib.contracts-runtime-server-mcp 2.0.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.
@@ -0,0 +1,181 @@
1
+ // src/mcp/registerTools.ts
2
+ import { defaultMcpTool } from "@contractspec/lib.contracts-spec/jsonschema";
3
+ function registerMcpTools(server, ops, ctx) {
4
+ for (const spec of ops.list()) {
5
+ if (spec.meta.kind !== "command")
6
+ continue;
7
+ const toolName = spec.transport?.mcp?.toolName ?? defaultMcpTool(spec.meta.key, spec.meta.version);
8
+ server.registerTool(toolName, {
9
+ description: spec.meta.description,
10
+ inputSchema: spec.io.input?.getZod()
11
+ }, async (args) => {
12
+ const result = await ops.execute(spec.meta.key, spec.meta.version, args ?? {}, ctx.toolCtx());
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(result, null, 4) }]
15
+ };
16
+ });
17
+ }
18
+ }
19
+
20
+ // src/mcp/registerResources.ts
21
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
22
+ import { Buffer } from "node:buffer";
23
+ function mcpResourceMeta(resource) {
24
+ return {
25
+ title: resource.meta.title,
26
+ description: resource.meta.description,
27
+ mimeType: resource.meta.mimeType,
28
+ _meta: { tags: resource.meta.tags ?? [] }
29
+ };
30
+ }
31
+ function registerMcpResources(server, resources, ctx) {
32
+ for (const resource of resources.listTemplates()) {
33
+ ctx.logger.debug("Registering resource: " + resource.meta.uriTemplate);
34
+ server.registerResource(resource.meta.uriTemplate, new ResourceTemplate(resource.meta.uriTemplate, { list: undefined }), mcpResourceMeta(resource), async (_uri, variables) => {
35
+ const parsedArgs = resource.input.parse(variables);
36
+ const out = await resource.resolve(parsedArgs, ctx.resourceCtx());
37
+ if (typeof out.data === "string") {
38
+ return {
39
+ contents: [
40
+ {
41
+ uri: out.uri,
42
+ mimeType: out.mimeType ?? resource.meta.mimeType,
43
+ text: out.data
44
+ }
45
+ ]
46
+ };
47
+ }
48
+ return {
49
+ contents: [
50
+ {
51
+ uri: out.uri,
52
+ mimeType: out.mimeType ?? resource.meta.mimeType,
53
+ blob: Buffer.from(out.data).toString("base64")
54
+ }
55
+ ]
56
+ };
57
+ });
58
+ ctx.logger.debug("Registered resource: " + resource.meta.uriTemplate);
59
+ }
60
+ }
61
+
62
+ // src/mcp/registerPrompts.ts
63
+ import { defaultMcpPrompt } from "@contractspec/lib.contracts-spec/jsonschema";
64
+ function promptArgsSchemaFromPromptArgs(args) {
65
+ const shape = {};
66
+ for (const a of args)
67
+ shape[a.name] = a.schema;
68
+ return shape;
69
+ }
70
+ function registerMcpPrompts(server, prompts, ctx) {
71
+ for (const prompt of prompts.list()) {
72
+ const promptName = defaultMcpPrompt(prompt.meta.key);
73
+ server.registerPrompt(promptName, {
74
+ title: prompt.meta.title,
75
+ description: prompt.meta.description,
76
+ argsSchema: promptArgsSchemaFromPromptArgs(prompt.args)
77
+ }, async (args) => {
78
+ const link = (tpl, vars) => {
79
+ let out = tpl;
80
+ for (const [k, v] of Object.entries(vars)) {
81
+ out = out.replace(new RegExp(`\\{${k}\\}`, "g"), encodeURIComponent(String(v)));
82
+ }
83
+ return out;
84
+ };
85
+ const parts = await prompt.render(prompt.input.parse(args), {
86
+ ...ctx.promptCtx(),
87
+ link
88
+ });
89
+ const text = parts.map((p) => p.type === "text" ? p.text : `See resource: ${p.title ?? p.uri}
90
+ URI: ${p.uri}`).join(`
91
+
92
+ `);
93
+ const contents = {
94
+ type: "text",
95
+ text
96
+ };
97
+ return {
98
+ messages: [{ role: "assistant", content: contents }],
99
+ description: prompt.meta.description
100
+ };
101
+ });
102
+ }
103
+ }
104
+
105
+ // src/mcp/registerPresentations.ts
106
+ import"@contractspec/lib.contracts-spec/presentations";
107
+ import {
108
+ createDefaultTransformEngine,
109
+ registerBasicValidation,
110
+ registerDefaultReactRenderer
111
+ } from "@contractspec/lib.contracts-spec/presentations/transform-engine";
112
+ import { sanitizeMcpName } from "@contractspec/lib.contracts-spec/jsonschema";
113
+ function isEngineRenderOutput(x) {
114
+ if (!x || typeof x !== "object")
115
+ return false;
116
+ return "body" in x && typeof x.body === "string";
117
+ }
118
+ function registerMcpPresentations(server, ctx) {
119
+ if (!ctx.presentations?.count()) {
120
+ return;
121
+ }
122
+ const engine = registerBasicValidation(registerDefaultReactRenderer(createDefaultTransformEngine()));
123
+ for (const presentationSpec of ctx.presentations.list()) {
124
+ const baseKey = sanitizeMcpName(`presentation_${presentationSpec.meta.key}_v${presentationSpec.meta.version}`);
125
+ const baseUri = `presentation://${presentationSpec.meta.key}/v${presentationSpec.meta.version}`;
126
+ ctx.logger.debug(`Registering presentation ${baseUri} for ${baseKey}`);
127
+ server.registerResource(baseKey, baseUri, {
128
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version}`,
129
+ description: presentationSpec.meta.description ?? "Presentation",
130
+ mimeType: "application/json"
131
+ }, async () => {
132
+ const jsonText = JSON.stringify({
133
+ meta: presentationSpec.meta,
134
+ source: presentationSpec.source,
135
+ targets: presentationSpec.targets
136
+ }, null, 2);
137
+ return {
138
+ contents: [
139
+ {
140
+ uri: baseUri,
141
+ mimeType: "application/json",
142
+ text: jsonText
143
+ }
144
+ ]
145
+ };
146
+ });
147
+ const variants = [{ ext: ".md", target: "markdown" }];
148
+ for (const v of variants) {
149
+ const key = `${baseKey}${v.ext}`;
150
+ const uri = `${baseUri}${v.ext}`;
151
+ server.registerResource(key, uri, {
152
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version} (${v.ext})`,
153
+ description: `${presentationSpec.meta.description ?? "Presentation"} (${v.ext})`
154
+ }, async () => {
155
+ const out = await engine.render(v.target, presentationSpec);
156
+ const mimeType = isEngineRenderOutput(out) && out.mimeType ? out.mimeType : v.target === "markdown" ? "text/markdown" : v.target;
157
+ const text = isEngineRenderOutput(out) && typeof out.body === "string" ? out.body : String(out);
158
+ return { contents: [{ uri, mimeType, text }] };
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ // src/mcp/createMcpServer.ts
165
+ function createMcpServer(server, ops, resources, prompts, ctxFactories) {
166
+ ctxFactories.logger.debug("Creating MCP server");
167
+ registerMcpTools(server, ops, { toolCtx: ctxFactories.toolCtx });
168
+ registerMcpResources(server, resources, {
169
+ logger: ctxFactories.logger,
170
+ resourceCtx: ctxFactories.resourceCtx
171
+ });
172
+ registerMcpPresentations(server, {
173
+ logger: ctxFactories.logger,
174
+ presentations: ctxFactories.presentations
175
+ });
176
+ registerMcpPrompts(server, prompts, { promptCtx: ctxFactories.promptCtx });
177
+ return server;
178
+ }
179
+ export {
180
+ createMcpServer
181
+ };
@@ -0,0 +1 @@
1
+ export { createMcpServer } from './mcp/createMcpServer';
@@ -0,0 +1,182 @@
1
+ // @bun
2
+ // src/mcp/registerTools.ts
3
+ import { defaultMcpTool } from "@contractspec/lib.contracts-spec/jsonschema";
4
+ function registerMcpTools(server, ops, ctx) {
5
+ for (const spec of ops.list()) {
6
+ if (spec.meta.kind !== "command")
7
+ continue;
8
+ const toolName = spec.transport?.mcp?.toolName ?? defaultMcpTool(spec.meta.key, spec.meta.version);
9
+ server.registerTool(toolName, {
10
+ description: spec.meta.description,
11
+ inputSchema: spec.io.input?.getZod()
12
+ }, async (args) => {
13
+ const result = await ops.execute(spec.meta.key, spec.meta.version, args ?? {}, ctx.toolCtx());
14
+ return {
15
+ content: [{ type: "text", text: JSON.stringify(result, null, 4) }]
16
+ };
17
+ });
18
+ }
19
+ }
20
+
21
+ // src/mcp/registerResources.ts
22
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
23
+ import { Buffer } from "buffer";
24
+ function mcpResourceMeta(resource) {
25
+ return {
26
+ title: resource.meta.title,
27
+ description: resource.meta.description,
28
+ mimeType: resource.meta.mimeType,
29
+ _meta: { tags: resource.meta.tags ?? [] }
30
+ };
31
+ }
32
+ function registerMcpResources(server, resources, ctx) {
33
+ for (const resource of resources.listTemplates()) {
34
+ ctx.logger.debug("Registering resource: " + resource.meta.uriTemplate);
35
+ server.registerResource(resource.meta.uriTemplate, new ResourceTemplate(resource.meta.uriTemplate, { list: undefined }), mcpResourceMeta(resource), async (_uri, variables) => {
36
+ const parsedArgs = resource.input.parse(variables);
37
+ const out = await resource.resolve(parsedArgs, ctx.resourceCtx());
38
+ if (typeof out.data === "string") {
39
+ return {
40
+ contents: [
41
+ {
42
+ uri: out.uri,
43
+ mimeType: out.mimeType ?? resource.meta.mimeType,
44
+ text: out.data
45
+ }
46
+ ]
47
+ };
48
+ }
49
+ return {
50
+ contents: [
51
+ {
52
+ uri: out.uri,
53
+ mimeType: out.mimeType ?? resource.meta.mimeType,
54
+ blob: Buffer.from(out.data).toString("base64")
55
+ }
56
+ ]
57
+ };
58
+ });
59
+ ctx.logger.debug("Registered resource: " + resource.meta.uriTemplate);
60
+ }
61
+ }
62
+
63
+ // src/mcp/registerPrompts.ts
64
+ import { defaultMcpPrompt } from "@contractspec/lib.contracts-spec/jsonschema";
65
+ function promptArgsSchemaFromPromptArgs(args) {
66
+ const shape = {};
67
+ for (const a of args)
68
+ shape[a.name] = a.schema;
69
+ return shape;
70
+ }
71
+ function registerMcpPrompts(server, prompts, ctx) {
72
+ for (const prompt of prompts.list()) {
73
+ const promptName = defaultMcpPrompt(prompt.meta.key);
74
+ server.registerPrompt(promptName, {
75
+ title: prompt.meta.title,
76
+ description: prompt.meta.description,
77
+ argsSchema: promptArgsSchemaFromPromptArgs(prompt.args)
78
+ }, async (args) => {
79
+ const link = (tpl, vars) => {
80
+ let out = tpl;
81
+ for (const [k, v] of Object.entries(vars)) {
82
+ out = out.replace(new RegExp(`\\{${k}\\}`, "g"), encodeURIComponent(String(v)));
83
+ }
84
+ return out;
85
+ };
86
+ const parts = await prompt.render(prompt.input.parse(args), {
87
+ ...ctx.promptCtx(),
88
+ link
89
+ });
90
+ const text = parts.map((p) => p.type === "text" ? p.text : `See resource: ${p.title ?? p.uri}
91
+ URI: ${p.uri}`).join(`
92
+
93
+ `);
94
+ const contents = {
95
+ type: "text",
96
+ text
97
+ };
98
+ return {
99
+ messages: [{ role: "assistant", content: contents }],
100
+ description: prompt.meta.description
101
+ };
102
+ });
103
+ }
104
+ }
105
+
106
+ // src/mcp/registerPresentations.ts
107
+ import"@contractspec/lib.contracts-spec/presentations";
108
+ import {
109
+ createDefaultTransformEngine,
110
+ registerBasicValidation,
111
+ registerDefaultReactRenderer
112
+ } from "@contractspec/lib.contracts-spec/presentations/transform-engine";
113
+ import { sanitizeMcpName } from "@contractspec/lib.contracts-spec/jsonschema";
114
+ function isEngineRenderOutput(x) {
115
+ if (!x || typeof x !== "object")
116
+ return false;
117
+ return "body" in x && typeof x.body === "string";
118
+ }
119
+ function registerMcpPresentations(server, ctx) {
120
+ if (!ctx.presentations?.count()) {
121
+ return;
122
+ }
123
+ const engine = registerBasicValidation(registerDefaultReactRenderer(createDefaultTransformEngine()));
124
+ for (const presentationSpec of ctx.presentations.list()) {
125
+ const baseKey = sanitizeMcpName(`presentation_${presentationSpec.meta.key}_v${presentationSpec.meta.version}`);
126
+ const baseUri = `presentation://${presentationSpec.meta.key}/v${presentationSpec.meta.version}`;
127
+ ctx.logger.debug(`Registering presentation ${baseUri} for ${baseKey}`);
128
+ server.registerResource(baseKey, baseUri, {
129
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version}`,
130
+ description: presentationSpec.meta.description ?? "Presentation",
131
+ mimeType: "application/json"
132
+ }, async () => {
133
+ const jsonText = JSON.stringify({
134
+ meta: presentationSpec.meta,
135
+ source: presentationSpec.source,
136
+ targets: presentationSpec.targets
137
+ }, null, 2);
138
+ return {
139
+ contents: [
140
+ {
141
+ uri: baseUri,
142
+ mimeType: "application/json",
143
+ text: jsonText
144
+ }
145
+ ]
146
+ };
147
+ });
148
+ const variants = [{ ext: ".md", target: "markdown" }];
149
+ for (const v of variants) {
150
+ const key = `${baseKey}${v.ext}`;
151
+ const uri = `${baseUri}${v.ext}`;
152
+ server.registerResource(key, uri, {
153
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version} (${v.ext})`,
154
+ description: `${presentationSpec.meta.description ?? "Presentation"} (${v.ext})`
155
+ }, async () => {
156
+ const out = await engine.render(v.target, presentationSpec);
157
+ const mimeType = isEngineRenderOutput(out) && out.mimeType ? out.mimeType : v.target === "markdown" ? "text/markdown" : v.target;
158
+ const text = isEngineRenderOutput(out) && typeof out.body === "string" ? out.body : String(out);
159
+ return { contents: [{ uri, mimeType, text }] };
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ // src/mcp/createMcpServer.ts
166
+ function createMcpServer(server, ops, resources, prompts, ctxFactories) {
167
+ ctxFactories.logger.debug("Creating MCP server");
168
+ registerMcpTools(server, ops, { toolCtx: ctxFactories.toolCtx });
169
+ registerMcpResources(server, resources, {
170
+ logger: ctxFactories.logger,
171
+ resourceCtx: ctxFactories.resourceCtx
172
+ });
173
+ registerMcpPresentations(server, {
174
+ logger: ctxFactories.logger,
175
+ presentations: ctxFactories.presentations
176
+ });
177
+ registerMcpPrompts(server, prompts, { promptCtx: ctxFactories.promptCtx });
178
+ return server;
179
+ }
180
+ export {
181
+ createMcpServer
182
+ };
package/package.json ADDED
@@ -0,0 +1,140 @@
1
+ {
2
+ "name": "@contractspec/lib.contracts-runtime-server-mcp",
3
+ "version": "2.0.0",
4
+ "description": "MCP server runtime adapters for ContractSpec contracts",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "scripts": {
8
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
9
+ "publish:pkg:canary": "bun publish:pkg --tag canary",
10
+ "clean": "rm -rf dist",
11
+ "lint": "bun run lint:fix",
12
+ "lint:fix": "eslint src --fix",
13
+ "lint:check": "eslint src",
14
+ "build": "bun run prebuild && bun run build:bundle && bun run build:types",
15
+ "build:bundle": "contractspec-bun-build transpile",
16
+ "build:types": "contractspec-bun-build types",
17
+ "prebuild": "contractspec-bun-build prebuild",
18
+ "typecheck": "tsc --noEmit",
19
+ "dev": "contractspec-bun-build dev"
20
+ },
21
+ "dependencies": {
22
+ "@contractspec/lib.contracts-spec": "2.0.0",
23
+ "@contractspec/lib.logger": "2.0.0",
24
+ "@modelcontextprotocol/sdk": "^1.26.0",
25
+ "zod": "^4.3.5"
26
+ },
27
+ "devDependencies": {
28
+ "@contractspec/tool.typescript": "2.0.0",
29
+ "typescript": "^5.9.3",
30
+ "@contractspec/tool.bun": "2.0.0"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "bun": "./dist/index.js",
40
+ "node": "./dist/node/index.js",
41
+ "default": "./dist/index.js"
42
+ },
43
+ "./mcp/createMcpServer": {
44
+ "types": "./dist/mcp/createMcpServer.d.ts",
45
+ "bun": "./dist/mcp/createMcpServer.js",
46
+ "node": "./dist/node/mcp/createMcpServer.js",
47
+ "default": "./dist/mcp/createMcpServer.js"
48
+ },
49
+ "./mcp/mcpTypes": {
50
+ "types": "./dist/mcp/mcpTypes.d.ts",
51
+ "bun": "./dist/mcp/mcpTypes.js",
52
+ "node": "./dist/node/mcp/mcpTypes.js",
53
+ "default": "./dist/mcp/mcpTypes.js"
54
+ },
55
+ "./mcp/registerPresentations": {
56
+ "types": "./dist/mcp/registerPresentations.d.ts",
57
+ "bun": "./dist/mcp/registerPresentations.js",
58
+ "node": "./dist/node/mcp/registerPresentations.js",
59
+ "default": "./dist/mcp/registerPresentations.js"
60
+ },
61
+ "./mcp/registerPrompts": {
62
+ "types": "./dist/mcp/registerPrompts.d.ts",
63
+ "bun": "./dist/mcp/registerPrompts.js",
64
+ "node": "./dist/node/mcp/registerPrompts.js",
65
+ "default": "./dist/mcp/registerPrompts.js"
66
+ },
67
+ "./mcp/registerResources": {
68
+ "types": "./dist/mcp/registerResources.d.ts",
69
+ "bun": "./dist/mcp/registerResources.js",
70
+ "node": "./dist/node/mcp/registerResources.js",
71
+ "default": "./dist/mcp/registerResources.js"
72
+ },
73
+ "./mcp/registerTools": {
74
+ "types": "./dist/mcp/registerTools.d.ts",
75
+ "bun": "./dist/mcp/registerTools.js",
76
+ "node": "./dist/node/mcp/registerTools.js",
77
+ "default": "./dist/mcp/registerTools.js"
78
+ },
79
+ "./provider-mcp": {
80
+ "types": "./dist/provider-mcp.d.ts",
81
+ "bun": "./dist/provider-mcp.js",
82
+ "node": "./dist/node/provider-mcp.js",
83
+ "default": "./dist/provider-mcp.js"
84
+ }
85
+ },
86
+ "publishConfig": {
87
+ "access": "public",
88
+ "registry": "https://registry.npmjs.org/",
89
+ "exports": {
90
+ ".": {
91
+ "types": "./dist/index.d.ts",
92
+ "bun": "./dist/index.js",
93
+ "node": "./dist/node/index.js",
94
+ "default": "./dist/index.js"
95
+ },
96
+ "./mcp/createMcpServer": {
97
+ "types": "./dist/mcp/createMcpServer.d.ts",
98
+ "bun": "./dist/mcp/createMcpServer.js",
99
+ "node": "./dist/node/mcp/createMcpServer.js",
100
+ "default": "./dist/mcp/createMcpServer.js"
101
+ },
102
+ "./mcp/mcpTypes": {
103
+ "types": "./dist/mcp/mcpTypes.d.ts",
104
+ "bun": "./dist/mcp/mcpTypes.js",
105
+ "node": "./dist/node/mcp/mcpTypes.js",
106
+ "default": "./dist/mcp/mcpTypes.js"
107
+ },
108
+ "./mcp/registerPresentations": {
109
+ "types": "./dist/mcp/registerPresentations.d.ts",
110
+ "bun": "./dist/mcp/registerPresentations.js",
111
+ "node": "./dist/node/mcp/registerPresentations.js",
112
+ "default": "./dist/mcp/registerPresentations.js"
113
+ },
114
+ "./mcp/registerPrompts": {
115
+ "types": "./dist/mcp/registerPrompts.d.ts",
116
+ "bun": "./dist/mcp/registerPrompts.js",
117
+ "node": "./dist/node/mcp/registerPrompts.js",
118
+ "default": "./dist/mcp/registerPrompts.js"
119
+ },
120
+ "./mcp/registerResources": {
121
+ "types": "./dist/mcp/registerResources.d.ts",
122
+ "bun": "./dist/mcp/registerResources.js",
123
+ "node": "./dist/node/mcp/registerResources.js",
124
+ "default": "./dist/mcp/registerResources.js"
125
+ },
126
+ "./mcp/registerTools": {
127
+ "types": "./dist/mcp/registerTools.d.ts",
128
+ "bun": "./dist/mcp/registerTools.js",
129
+ "node": "./dist/node/mcp/registerTools.js",
130
+ "default": "./dist/mcp/registerTools.js"
131
+ },
132
+ "./provider-mcp": {
133
+ "types": "./dist/provider-mcp.d.ts",
134
+ "bun": "./dist/provider-mcp.js",
135
+ "node": "./dist/node/provider-mcp.js",
136
+ "default": "./dist/provider-mcp.js"
137
+ }
138
+ }
139
+ }
140
+ }