@derive-to/mcp 0.5.1 → 0.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/LICENSE +1 -1
- package/README.md +79 -0
- package/SKILL.md +133 -126
- package/package.json +14 -8
- package/references/compatibility.md +23 -0
- package/references/connect.md +66 -0
- package/src/client.ts +311 -45
- package/src/filename.ts +30 -0
- package/src/index.ts +355 -51
- package/src/template-resources.ts +201 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { catalogResource, templateResource } from "@derive-to/templates"
|
|
2
|
+
import { type McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
3
|
+
import type { Variables } from "@modelcontextprotocol/sdk/shared/uriTemplate.js"
|
|
4
|
+
import type { DeriveClient, TemplateLibraryEntryJson, TemplateLibraryJson } from "./client"
|
|
5
|
+
|
|
6
|
+
// Kept deliberately small so the executable stdio entry and its test share the
|
|
7
|
+
// registration contract. The remote server reads the same data directly from the
|
|
8
|
+
// portable package; neither server owns a second catalog.
|
|
9
|
+
export type TemplateResourceRegistrar = Pick<McpServer, "registerResource">
|
|
10
|
+
|
|
11
|
+
const TEMPLATE_LIBRARY_CATALOG_URI = "derive://template-libraries"
|
|
12
|
+
const TEMPLATE_LIBRARY_ID = /^[A-Za-z0-9_-]+$/
|
|
13
|
+
// MCP is intentionally an HTTP-only client and cannot import core at runtime.
|
|
14
|
+
// Keep one validating formatter at this boundary instead of interpolating URIs
|
|
15
|
+
// throughout each resource serializer.
|
|
16
|
+
const templateLibraryUri = (libraryId: string, entryId?: string): string => {
|
|
17
|
+
if (!TEMPLATE_LIBRARY_ID.test(libraryId) || (entryId && !TEMPLATE_LIBRARY_ID.test(entryId)))
|
|
18
|
+
throw new Error("invalid template library URI id")
|
|
19
|
+
return entryId
|
|
20
|
+
? `${TEMPLATE_LIBRARY_CATALOG_URI}/${libraryId}/${entryId}`
|
|
21
|
+
: `${TEMPLATE_LIBRARY_CATALOG_URI}/${libraryId}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerTemplateResources(server: TemplateResourceRegistrar): void {
|
|
25
|
+
const catalog = catalogResource()
|
|
26
|
+
server.registerResource(
|
|
27
|
+
"templates:catalog",
|
|
28
|
+
catalog.uri,
|
|
29
|
+
{
|
|
30
|
+
title: catalog.title,
|
|
31
|
+
description: catalog.description,
|
|
32
|
+
mimeType: catalog.mimeType,
|
|
33
|
+
annotations: { audience: ["assistant"], priority: 0.85 },
|
|
34
|
+
},
|
|
35
|
+
async (uri: URL) => ({
|
|
36
|
+
contents: [{ uri: uri.href, mimeType: catalog.mimeType, text: catalog.text }],
|
|
37
|
+
}),
|
|
38
|
+
)
|
|
39
|
+
server.registerResource(
|
|
40
|
+
"templates:entry",
|
|
41
|
+
new ResourceTemplate("derive://templates/{id}", { list: undefined }),
|
|
42
|
+
{
|
|
43
|
+
title: "Derive built-in Template",
|
|
44
|
+
description: "One exact built-in starter with metadata and immutable provenance.",
|
|
45
|
+
mimeType: "application/json",
|
|
46
|
+
annotations: { audience: ["assistant"], priority: 0.7 },
|
|
47
|
+
},
|
|
48
|
+
async (uri, variables) => {
|
|
49
|
+
const id = pathVariable(variables, "id")
|
|
50
|
+
const resource = templateResource(id)
|
|
51
|
+
if (!resource) throw new Error(`No built-in template "${id}".`)
|
|
52
|
+
return {
|
|
53
|
+
contents: [{ uri: uri.href, mimeType: resource.mimeType, text: resource.text }],
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const libraryEntry = (library: TemplateLibraryJson, entry: TemplateLibraryEntryJson) => ({
|
|
60
|
+
...entry,
|
|
61
|
+
uri: templateLibraryUri(library.id, entry.id),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const pathVariable = (variables: Variables, name: string): string => {
|
|
65
|
+
const value = variables[name]
|
|
66
|
+
if (typeof value !== "string") throw new Error(`missing URI variable ${name}`)
|
|
67
|
+
return value
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const templateLibraryPage = async (uri: URL, client: DeriveClient, cursor?: string) => {
|
|
71
|
+
const page = await client.listTemplateLibraries(cursor)
|
|
72
|
+
const nextUri = page.next_cursor
|
|
73
|
+
? `${TEMPLATE_LIBRARY_CATALOG_URI}?cursor=${encodeURIComponent(page.next_cursor)}`
|
|
74
|
+
: undefined
|
|
75
|
+
return {
|
|
76
|
+
contents: [
|
|
77
|
+
{
|
|
78
|
+
uri: uri.href,
|
|
79
|
+
mimeType: "application/json" as const,
|
|
80
|
+
text: JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
libraries: page.libraries.map((library) => ({
|
|
83
|
+
id: library.id,
|
|
84
|
+
title: library.title,
|
|
85
|
+
description: library.description,
|
|
86
|
+
scope: library.scope,
|
|
87
|
+
entry_count: library.entry_count,
|
|
88
|
+
uri: templateLibraryUri(library.id),
|
|
89
|
+
})),
|
|
90
|
+
truncated: page.truncated,
|
|
91
|
+
next_uri: nextUri,
|
|
92
|
+
next: nextUri ? "Read next_uri for the next catalog page." : undefined,
|
|
93
|
+
},
|
|
94
|
+
null,
|
|
95
|
+
2,
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Register one lazy catalog plus URI templates; startup cost stays constant. */
|
|
103
|
+
export function registerWorkspaceTemplateResources(
|
|
104
|
+
server: TemplateResourceRegistrar,
|
|
105
|
+
client: DeriveClient,
|
|
106
|
+
): void {
|
|
107
|
+
server.registerResource(
|
|
108
|
+
"template-libraries:catalog",
|
|
109
|
+
TEMPLATE_LIBRARY_CATALOG_URI,
|
|
110
|
+
{
|
|
111
|
+
title: "Derive template libraries",
|
|
112
|
+
description:
|
|
113
|
+
"Accessible public, workspace, and personal template libraries. Read a library URI for entries, then an entry URI for its pinned starter source.",
|
|
114
|
+
mimeType: "application/json",
|
|
115
|
+
annotations: { audience: ["assistant"], priority: 0.82 },
|
|
116
|
+
},
|
|
117
|
+
async (uri: URL) => templateLibraryPage(uri, client),
|
|
118
|
+
)
|
|
119
|
+
server.registerResource(
|
|
120
|
+
"template-libraries:page",
|
|
121
|
+
new ResourceTemplate(`${TEMPLATE_LIBRARY_CATALOG_URI}{?cursor}`, { list: undefined }),
|
|
122
|
+
{
|
|
123
|
+
title: "Derive template library catalog page",
|
|
124
|
+
description: "Continue an accessible template-library catalog from its next cursor.",
|
|
125
|
+
mimeType: "application/json",
|
|
126
|
+
annotations: { audience: ["assistant"], priority: 0.78 },
|
|
127
|
+
},
|
|
128
|
+
async (uri, variables) =>
|
|
129
|
+
templateLibraryPage(uri, client, decodeURIComponent(pathVariable(variables, "cursor"))),
|
|
130
|
+
)
|
|
131
|
+
server.registerResource(
|
|
132
|
+
"template-library",
|
|
133
|
+
new ResourceTemplate(`${TEMPLATE_LIBRARY_CATALOG_URI}/{libraryId}`, { list: undefined }),
|
|
134
|
+
{
|
|
135
|
+
title: "Derive template library",
|
|
136
|
+
description: "One accessible authored library and its starter URIs.",
|
|
137
|
+
mimeType: "application/json",
|
|
138
|
+
annotations: { audience: ["assistant"], priority: 0.76 },
|
|
139
|
+
},
|
|
140
|
+
async (uri, variables) => {
|
|
141
|
+
const current = await client.getTemplateLibrary(pathVariable(variables, "libraryId"))
|
|
142
|
+
return {
|
|
143
|
+
contents: [
|
|
144
|
+
{
|
|
145
|
+
uri: uri.href,
|
|
146
|
+
mimeType: "application/json" as const,
|
|
147
|
+
text: JSON.stringify(
|
|
148
|
+
{
|
|
149
|
+
...current,
|
|
150
|
+
entries: (current.entries ?? []).map((entry) => libraryEntry(current, entry)),
|
|
151
|
+
},
|
|
152
|
+
null,
|
|
153
|
+
2,
|
|
154
|
+
),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
server.registerResource(
|
|
161
|
+
"template-library-entry",
|
|
162
|
+
new ResourceTemplate(`${TEMPLATE_LIBRARY_CATALOG_URI}/{libraryId}/{entryId}`, {
|
|
163
|
+
list: undefined,
|
|
164
|
+
}),
|
|
165
|
+
{
|
|
166
|
+
title: "Derive template starter",
|
|
167
|
+
description: "Pinned source to adapt into a new independent artifact.",
|
|
168
|
+
mimeType: "application/json",
|
|
169
|
+
annotations: { audience: ["assistant"], priority: 0.74 },
|
|
170
|
+
},
|
|
171
|
+
async (uri, variables) => {
|
|
172
|
+
const libraryId = pathVariable(variables, "libraryId")
|
|
173
|
+
const entryId = pathVariable(variables, "entryId")
|
|
174
|
+
const [library, starter] = await Promise.all([
|
|
175
|
+
client.getTemplateLibrary(libraryId),
|
|
176
|
+
client.getTemplateStarter(libraryId, entryId),
|
|
177
|
+
])
|
|
178
|
+
return {
|
|
179
|
+
contents: [
|
|
180
|
+
{
|
|
181
|
+
uri: uri.href,
|
|
182
|
+
mimeType: "application/json" as const,
|
|
183
|
+
text: JSON.stringify(
|
|
184
|
+
{
|
|
185
|
+
...starter.entry,
|
|
186
|
+
starter: {
|
|
187
|
+
source: starter.source,
|
|
188
|
+
filename: `${starter.entry.id}.${starter.entry.format}`,
|
|
189
|
+
mime_type: starter.entry.format === "md" ? "text/markdown" : "text/html",
|
|
190
|
+
message: `Created from ${library.title}/${starter.entry.title} · source v${starter.entry.source_version}`,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
null,
|
|
194
|
+
2,
|
|
195
|
+
),
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
}
|