@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 +3 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +186 -0
- package/dist/mcp/createMcpServer.d.ts +6 -0
- package/dist/mcp/createMcpServer.js +182 -0
- package/dist/mcp/mcpTypes.d.ts +18 -0
- package/dist/mcp/mcpTypes.js +1 -0
- package/dist/mcp/registerPresentations.d.ts +3 -0
- package/dist/mcp/registerPresentations.js +62 -0
- package/dist/mcp/registerPrompts.d.ts +4 -0
- package/dist/mcp/registerPrompts.js +46 -0
- package/dist/mcp/registerResources.d.ts +4 -0
- package/dist/mcp/registerResources.js +45 -0
- package/dist/mcp/registerTools.d.ts +4 -0
- package/dist/mcp/registerTools.js +22 -0
- package/dist/node/index.js +185 -0
- package/dist/node/mcp/createMcpServer.js +181 -0
- package/dist/node/mcp/mcpTypes.js +0 -0
- package/dist/node/mcp/registerPresentations.js +61 -0
- package/dist/node/mcp/registerPrompts.js +45 -0
- package/dist/node/mcp/registerResources.js +44 -0
- package/dist/node/mcp/registerTools.js +21 -0
- package/dist/node/provider-mcp.js +181 -0
- package/dist/provider-mcp.d.ts +1 -0
- package/dist/provider-mcp.js +182 -0
- package/package.json +140 -0
|
@@ -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
|
+
};
|