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