@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,185 @@
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
+ registerMcpTools,
181
+ registerMcpResources,
182
+ registerMcpPrompts,
183
+ registerMcpPresentations,
184
+ createMcpServer
185
+ };
@@ -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
+ };
File without changes
@@ -0,0 +1,61 @@
1
+ // src/mcp/registerPresentations.ts
2
+ import"@contractspec/lib.contracts-spec/presentations";
3
+ import {
4
+ createDefaultTransformEngine,
5
+ registerBasicValidation,
6
+ registerDefaultReactRenderer
7
+ } from "@contractspec/lib.contracts-spec/presentations/transform-engine";
8
+ import { sanitizeMcpName } from "@contractspec/lib.contracts-spec/jsonschema";
9
+ function isEngineRenderOutput(x) {
10
+ if (!x || typeof x !== "object")
11
+ return false;
12
+ return "body" in x && typeof x.body === "string";
13
+ }
14
+ function registerMcpPresentations(server, ctx) {
15
+ if (!ctx.presentations?.count()) {
16
+ return;
17
+ }
18
+ const engine = registerBasicValidation(registerDefaultReactRenderer(createDefaultTransformEngine()));
19
+ for (const presentationSpec of ctx.presentations.list()) {
20
+ const baseKey = sanitizeMcpName(`presentation_${presentationSpec.meta.key}_v${presentationSpec.meta.version}`);
21
+ const baseUri = `presentation://${presentationSpec.meta.key}/v${presentationSpec.meta.version}`;
22
+ ctx.logger.debug(`Registering presentation ${baseUri} for ${baseKey}`);
23
+ server.registerResource(baseKey, baseUri, {
24
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version}`,
25
+ description: presentationSpec.meta.description ?? "Presentation",
26
+ mimeType: "application/json"
27
+ }, async () => {
28
+ const jsonText = JSON.stringify({
29
+ meta: presentationSpec.meta,
30
+ source: presentationSpec.source,
31
+ targets: presentationSpec.targets
32
+ }, null, 2);
33
+ return {
34
+ contents: [
35
+ {
36
+ uri: baseUri,
37
+ mimeType: "application/json",
38
+ text: jsonText
39
+ }
40
+ ]
41
+ };
42
+ });
43
+ const variants = [{ ext: ".md", target: "markdown" }];
44
+ for (const v of variants) {
45
+ const key = `${baseKey}${v.ext}`;
46
+ const uri = `${baseUri}${v.ext}`;
47
+ server.registerResource(key, uri, {
48
+ title: `${presentationSpec.meta.key} v${presentationSpec.meta.version} (${v.ext})`,
49
+ description: `${presentationSpec.meta.description ?? "Presentation"} (${v.ext})`
50
+ }, async () => {
51
+ const out = await engine.render(v.target, presentationSpec);
52
+ const mimeType = isEngineRenderOutput(out) && out.mimeType ? out.mimeType : v.target === "markdown" ? "text/markdown" : v.target;
53
+ const text = isEngineRenderOutput(out) && typeof out.body === "string" ? out.body : String(out);
54
+ return { contents: [{ uri, mimeType, text }] };
55
+ });
56
+ }
57
+ }
58
+ }
59
+ export {
60
+ registerMcpPresentations
61
+ };
@@ -0,0 +1,45 @@
1
+ // src/mcp/registerPrompts.ts
2
+ import { defaultMcpPrompt } from "@contractspec/lib.contracts-spec/jsonschema";
3
+ function promptArgsSchemaFromPromptArgs(args) {
4
+ const shape = {};
5
+ for (const a of args)
6
+ shape[a.name] = a.schema;
7
+ return shape;
8
+ }
9
+ function registerMcpPrompts(server, prompts, ctx) {
10
+ for (const prompt of prompts.list()) {
11
+ const promptName = defaultMcpPrompt(prompt.meta.key);
12
+ server.registerPrompt(promptName, {
13
+ title: prompt.meta.title,
14
+ description: prompt.meta.description,
15
+ argsSchema: promptArgsSchemaFromPromptArgs(prompt.args)
16
+ }, async (args) => {
17
+ const link = (tpl, vars) => {
18
+ let out = tpl;
19
+ for (const [k, v] of Object.entries(vars)) {
20
+ out = out.replace(new RegExp(`\\{${k}\\}`, "g"), encodeURIComponent(String(v)));
21
+ }
22
+ return out;
23
+ };
24
+ const parts = await prompt.render(prompt.input.parse(args), {
25
+ ...ctx.promptCtx(),
26
+ link
27
+ });
28
+ const text = parts.map((p) => p.type === "text" ? p.text : `See resource: ${p.title ?? p.uri}
29
+ URI: ${p.uri}`).join(`
30
+
31
+ `);
32
+ const contents = {
33
+ type: "text",
34
+ text
35
+ };
36
+ return {
37
+ messages: [{ role: "assistant", content: contents }],
38
+ description: prompt.meta.description
39
+ };
40
+ });
41
+ }
42
+ }
43
+ export {
44
+ registerMcpPrompts
45
+ };
@@ -0,0 +1,44 @@
1
+ // src/mcp/registerResources.ts
2
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { Buffer } from "node:buffer";
4
+ function mcpResourceMeta(resource) {
5
+ return {
6
+ title: resource.meta.title,
7
+ description: resource.meta.description,
8
+ mimeType: resource.meta.mimeType,
9
+ _meta: { tags: resource.meta.tags ?? [] }
10
+ };
11
+ }
12
+ function registerMcpResources(server, resources, ctx) {
13
+ for (const resource of resources.listTemplates()) {
14
+ ctx.logger.debug("Registering resource: " + resource.meta.uriTemplate);
15
+ server.registerResource(resource.meta.uriTemplate, new ResourceTemplate(resource.meta.uriTemplate, { list: undefined }), mcpResourceMeta(resource), async (_uri, variables) => {
16
+ const parsedArgs = resource.input.parse(variables);
17
+ const out = await resource.resolve(parsedArgs, ctx.resourceCtx());
18
+ if (typeof out.data === "string") {
19
+ return {
20
+ contents: [
21
+ {
22
+ uri: out.uri,
23
+ mimeType: out.mimeType ?? resource.meta.mimeType,
24
+ text: out.data
25
+ }
26
+ ]
27
+ };
28
+ }
29
+ return {
30
+ contents: [
31
+ {
32
+ uri: out.uri,
33
+ mimeType: out.mimeType ?? resource.meta.mimeType,
34
+ blob: Buffer.from(out.data).toString("base64")
35
+ }
36
+ ]
37
+ };
38
+ });
39
+ ctx.logger.debug("Registered resource: " + resource.meta.uriTemplate);
40
+ }
41
+ }
42
+ export {
43
+ registerMcpResources
44
+ };
@@ -0,0 +1,21 @@
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
+ export {
20
+ registerMcpTools
21
+ };