@mcp-rune/create 0.11.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.
Files changed (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -0
  3. package/bin/rune.js +7 -0
  4. package/dist/commands/add-model.d.ts +5 -0
  5. package/dist/commands/add-model.d.ts.map +1 -0
  6. package/dist/commands/add-model.js +131 -0
  7. package/dist/commands/add-model.js.map +1 -0
  8. package/dist/commands/db-up.d.ts +2 -0
  9. package/dist/commands/db-up.d.ts.map +1 -0
  10. package/dist/commands/db-up.js +29 -0
  11. package/dist/commands/db-up.js.map +1 -0
  12. package/dist/commands/doctor/env-checks.d.ts +4 -0
  13. package/dist/commands/doctor/env-checks.d.ts.map +1 -0
  14. package/dist/commands/doctor/env-checks.js +88 -0
  15. package/dist/commands/doctor/env-checks.js.map +1 -0
  16. package/dist/commands/doctor/index.d.ts +21 -0
  17. package/dist/commands/doctor/index.d.ts.map +1 -0
  18. package/dist/commands/doctor/index.js +44 -0
  19. package/dist/commands/doctor/index.js.map +1 -0
  20. package/dist/commands/doctor/project-validation.d.ts +5 -0
  21. package/dist/commands/doctor/project-validation.d.ts.map +1 -0
  22. package/dist/commands/doctor/project-validation.js +166 -0
  23. package/dist/commands/doctor/project-validation.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +7 -0
  25. package/dist/commands/doctor.d.ts.map +1 -0
  26. package/dist/commands/doctor.js +306 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/inspect.d.ts +16 -0
  29. package/dist/commands/inspect.d.ts.map +1 -0
  30. package/dist/commands/inspect.js +66 -0
  31. package/dist/commands/inspect.js.map +1 -0
  32. package/dist/commands/new/actions/apps.d.ts +5 -0
  33. package/dist/commands/new/actions/apps.d.ts.map +1 -0
  34. package/dist/commands/new/actions/apps.js +51 -0
  35. package/dist/commands/new/actions/apps.js.map +1 -0
  36. package/dist/commands/new/actions/architecture.d.ts +5 -0
  37. package/dist/commands/new/actions/architecture.d.ts.map +1 -0
  38. package/dist/commands/new/actions/architecture.js +45 -0
  39. package/dist/commands/new/actions/architecture.js.map +1 -0
  40. package/dist/commands/new/actions/auth.d.ts +5 -0
  41. package/dist/commands/new/actions/auth.d.ts.map +1 -0
  42. package/dist/commands/new/actions/auth.js +30 -0
  43. package/dist/commands/new/actions/auth.js.map +1 -0
  44. package/dist/commands/new/actions/database.d.ts +5 -0
  45. package/dist/commands/new/actions/database.d.ts.map +1 -0
  46. package/dist/commands/new/actions/database.js +60 -0
  47. package/dist/commands/new/actions/database.js.map +1 -0
  48. package/dist/commands/new/actions/fetch-template.d.ts +5 -0
  49. package/dist/commands/new/actions/fetch-template.d.ts.map +1 -0
  50. package/dist/commands/new/actions/fetch-template.js +22 -0
  51. package/dist/commands/new/actions/fetch-template.js.map +1 -0
  52. package/dist/commands/new/actions/intro.d.ts +5 -0
  53. package/dist/commands/new/actions/intro.d.ts.map +1 -0
  54. package/dist/commands/new/actions/intro.js +7 -0
  55. package/dist/commands/new/actions/intro.js.map +1 -0
  56. package/dist/commands/new/actions/layers.d.ts +5 -0
  57. package/dist/commands/new/actions/layers.d.ts.map +1 -0
  58. package/dist/commands/new/actions/layers.js +38 -0
  59. package/dist/commands/new/actions/layers.js.map +1 -0
  60. package/dist/commands/new/actions/models.d.ts +5 -0
  61. package/dist/commands/new/actions/models.d.ts.map +1 -0
  62. package/dist/commands/new/actions/models.js +18 -0
  63. package/dist/commands/new/actions/models.js.map +1 -0
  64. package/dist/commands/new/actions/next-steps.d.ts +5 -0
  65. package/dist/commands/new/actions/next-steps.d.ts.map +1 -0
  66. package/dist/commands/new/actions/next-steps.js +38 -0
  67. package/dist/commands/new/actions/next-steps.js.map +1 -0
  68. package/dist/commands/new/actions/observability.d.ts +5 -0
  69. package/dist/commands/new/actions/observability.d.ts.map +1 -0
  70. package/dist/commands/new/actions/observability.js +45 -0
  71. package/dist/commands/new/actions/observability.js.map +1 -0
  72. package/dist/commands/new/actions/post-scaffold.d.ts +5 -0
  73. package/dist/commands/new/actions/post-scaffold.d.ts.map +1 -0
  74. package/dist/commands/new/actions/post-scaffold.js +81 -0
  75. package/dist/commands/new/actions/post-scaffold.js.map +1 -0
  76. package/dist/commands/new/actions/preset.d.ts +5 -0
  77. package/dist/commands/new/actions/preset.d.ts.map +1 -0
  78. package/dist/commands/new/actions/preset.js +23 -0
  79. package/dist/commands/new/actions/preset.js.map +1 -0
  80. package/dist/commands/new/actions/prompts.d.ts +5 -0
  81. package/dist/commands/new/actions/prompts.d.ts.map +1 -0
  82. package/dist/commands/new/actions/prompts.js +33 -0
  83. package/dist/commands/new/actions/prompts.js.map +1 -0
  84. package/dist/commands/new/actions/render.d.ts +5 -0
  85. package/dist/commands/new/actions/render.d.ts.map +1 -0
  86. package/dist/commands/new/actions/render.js +35 -0
  87. package/dist/commands/new/actions/render.js.map +1 -0
  88. package/dist/commands/new/actions/scaffold-header.d.ts +5 -0
  89. package/dist/commands/new/actions/scaffold-header.d.ts.map +1 -0
  90. package/dist/commands/new/actions/scaffold-header.js +19 -0
  91. package/dist/commands/new/actions/scaffold-header.js.map +1 -0
  92. package/dist/commands/new/actions/scaffold-mode.d.ts +5 -0
  93. package/dist/commands/new/actions/scaffold-mode.d.ts.map +1 -0
  94. package/dist/commands/new/actions/scaffold-mode.js +49 -0
  95. package/dist/commands/new/actions/scaffold-mode.js.map +1 -0
  96. package/dist/commands/new/actions/summary.d.ts +5 -0
  97. package/dist/commands/new/actions/summary.d.ts.map +1 -0
  98. package/dist/commands/new/actions/summary.js +71 -0
  99. package/dist/commands/new/actions/summary.js.map +1 -0
  100. package/dist/commands/new/actions/toggles.d.ts +5 -0
  101. package/dist/commands/new/actions/toggles.d.ts.map +1 -0
  102. package/dist/commands/new/actions/toggles.js +25 -0
  103. package/dist/commands/new/actions/toggles.js.map +1 -0
  104. package/dist/commands/new/actions/tools.d.ts +5 -0
  105. package/dist/commands/new/actions/tools.d.ts.map +1 -0
  106. package/dist/commands/new/actions/tools.js +36 -0
  107. package/dist/commands/new/actions/tools.js.map +1 -0
  108. package/dist/commands/new/actions/transport.d.ts +5 -0
  109. package/dist/commands/new/actions/transport.d.ts.map +1 -0
  110. package/dist/commands/new/actions/transport.js +24 -0
  111. package/dist/commands/new/actions/transport.js.map +1 -0
  112. package/dist/commands/new/context.d.ts +76 -0
  113. package/dist/commands/new/context.d.ts.map +1 -0
  114. package/dist/commands/new/context.js +134 -0
  115. package/dist/commands/new/context.js.map +1 -0
  116. package/dist/commands/new/index.d.ts +5 -0
  117. package/dist/commands/new/index.d.ts.map +1 -0
  118. package/dist/commands/new/index.js +18 -0
  119. package/dist/commands/new/index.js.map +1 -0
  120. package/dist/commands/new/pipeline.d.ts +12 -0
  121. package/dist/commands/new/pipeline.d.ts.map +1 -0
  122. package/dist/commands/new/pipeline.js +67 -0
  123. package/dist/commands/new/pipeline.js.map +1 -0
  124. package/dist/commands/new/presets.d.ts +40 -0
  125. package/dist/commands/new/presets.d.ts.map +1 -0
  126. package/dist/commands/new/presets.js +91 -0
  127. package/dist/commands/new/presets.js.map +1 -0
  128. package/dist/commands/new.d.ts +24 -0
  129. package/dist/commands/new.d.ts.map +1 -0
  130. package/dist/commands/new.js +162 -0
  131. package/dist/commands/new.js.map +1 -0
  132. package/dist/commands/post-scaffold.d.ts +6 -0
  133. package/dist/commands/post-scaffold.d.ts.map +1 -0
  134. package/dist/commands/post-scaffold.js +24 -0
  135. package/dist/commands/post-scaffold.js.map +1 -0
  136. package/dist/core/cancel.d.ts +3 -0
  137. package/dist/core/cancel.d.ts.map +1 -0
  138. package/dist/core/cancel.js +17 -0
  139. package/dist/core/cancel.js.map +1 -0
  140. package/dist/core/color.d.ts +12 -0
  141. package/dist/core/color.d.ts.map +1 -0
  142. package/dist/core/color.js +14 -0
  143. package/dist/core/color.js.map +1 -0
  144. package/dist/core/db-setup.d.ts +13 -0
  145. package/dist/core/db-setup.d.ts.map +1 -0
  146. package/dist/core/db-setup.js +63 -0
  147. package/dist/core/db-setup.js.map +1 -0
  148. package/dist/core/fs-utils.d.ts +4 -0
  149. package/dist/core/fs-utils.d.ts.map +1 -0
  150. package/dist/core/fs-utils.js +31 -0
  151. package/dist/core/fs-utils.js.map +1 -0
  152. package/dist/core/output.d.ts +19 -0
  153. package/dist/core/output.d.ts.map +1 -0
  154. package/dist/core/output.js +42 -0
  155. package/dist/core/output.js.map +1 -0
  156. package/dist/core/prompts.d.ts +2 -0
  157. package/dist/core/prompts.d.ts.map +1 -0
  158. package/dist/core/prompts.js +2 -0
  159. package/dist/core/prompts.js.map +1 -0
  160. package/dist/core/tasks.d.ts +11 -0
  161. package/dist/core/tasks.d.ts.map +1 -0
  162. package/dist/core/tasks.js +28 -0
  163. package/dist/core/tasks.js.map +1 -0
  164. package/dist/data/mascot.d.ts +13 -0
  165. package/dist/data/mascot.d.ts.map +1 -0
  166. package/dist/data/mascot.js +80 -0
  167. package/dist/data/mascot.js.map +1 -0
  168. package/dist/index.d.ts +4 -0
  169. package/dist/index.d.ts.map +1 -0
  170. package/dist/index.js +68 -0
  171. package/dist/index.js.map +1 -0
  172. package/dist/render/copy-tree.d.ts +5 -0
  173. package/dist/render/copy-tree.d.ts.map +1 -0
  174. package/dist/render/copy-tree.js +146 -0
  175. package/dist/render/copy-tree.js.map +1 -0
  176. package/dist/render/fetch-template.d.ts +9 -0
  177. package/dist/render/fetch-template.d.ts.map +1 -0
  178. package/dist/render/fetch-template.js +113 -0
  179. package/dist/render/fetch-template.js.map +1 -0
  180. package/dist/render/model-gen.d.ts +3 -0
  181. package/dist/render/model-gen.d.ts.map +1 -0
  182. package/dist/render/model-gen.js +23 -0
  183. package/dist/render/model-gen.js.map +1 -0
  184. package/dist/templates/registry.d.ts +14 -0
  185. package/dist/templates/registry.d.ts.map +1 -0
  186. package/dist/templates/registry.js +34 -0
  187. package/dist/templates/registry.js.map +1 -0
  188. package/dist/types.d.ts +87 -0
  189. package/dist/types.d.ts.map +1 -0
  190. package/dist/types.js +8 -0
  191. package/dist/types.js.map +1 -0
  192. package/dist/wizard/presets.d.ts +35 -0
  193. package/dist/wizard/presets.d.ts.map +1 -0
  194. package/dist/wizard/presets.js +67 -0
  195. package/dist/wizard/presets.js.map +1 -0
  196. package/dist/wizard/questions.d.ts +11 -0
  197. package/dist/wizard/questions.d.ts.map +1 -0
  198. package/dist/wizard/questions.js +154 -0
  199. package/dist/wizard/questions.js.map +1 -0
  200. package/package.json +52 -0
  201. package/templates/advanced/.env.example.ejs +82 -0
  202. package/templates/advanced/.node-version +1 -0
  203. package/templates/advanced/README.md.ejs +76 -0
  204. package/templates/advanced/__only_if_hasHttp__/src/servers/remote.ts.ejs +36 -0
  205. package/templates/advanced/__only_if_hasStdio__/src/servers/local.ts +30 -0
  206. package/templates/advanced/__only_if_useAxiosClient__/src/api/axios-client.ts +74 -0
  207. package/templates/advanced/__only_if_useCustomApiClient__/src/api/custom-client.ts +48 -0
  208. package/templates/advanced/__only_if_useCustomConvention__/src/conventions/custom-convention.ts +64 -0
  209. package/templates/advanced/__only_if_useCustomSearch__/src/api-extensions/custom-search-adapter.ts +30 -0
  210. package/templates/advanced/__only_if_useFetchClient__/src/api/fetch-client.ts +111 -0
  211. package/templates/advanced/__only_if_useFlatRestConvention__/src/conventions/flat-rest-convention.ts +85 -0
  212. package/templates/advanced/__only_if_usePinoLogger__/src/observability/logger.ts +62 -0
  213. package/templates/advanced/__only_if_useRansackSearch__/src/api-extensions/ransack-search-adapter.ts +41 -0
  214. package/templates/advanced/__only_if_useSharedModelAttrs__/src/models/app-base-model.ts +22 -0
  215. package/templates/advanced/__only_if_useVectorStorage__/src/storage/vector-store.ts +21 -0
  216. package/templates/advanced/__only_if_withAnalysis__/docker-compose.yml +18 -0
  217. package/templates/advanced/__only_if_withDomain__/src/domain/registry.ts +25 -0
  218. package/templates/advanced/config/schema.ts.ejs +126 -0
  219. package/templates/advanced/package.json.ejs +50 -0
  220. package/templates/advanced/src/config.ts.ejs +207 -0
  221. package/templates/advanced/src/db.ts.ejs +35 -0
  222. package/templates/advanced/src/models/_model_.ts.ejs +25 -0
  223. package/templates/advanced/src/models/index.ts.ejs +13 -0
  224. package/templates/advanced/src/profiles.ts +44 -0
  225. package/templates/advanced/src/prompts/_model_-prompt.ts.ejs +27 -0
  226. package/templates/advanced/src/prompts/index.ts.ejs +18 -0
  227. package/templates/advanced/src/scripts/db-migrate.ts +90 -0
  228. package/templates/advanced/src/tools/index.ts.ejs +133 -0
  229. package/templates/advanced/test/smoke.test.ts +16 -0
  230. package/templates/advanced/tsconfig.json +14 -0
  231. package/templates/simple/.env.example +3 -0
  232. package/templates/simple/.node-version +1 -0
  233. package/templates/simple/README.md +40 -0
  234. package/templates/simple/package.json.ejs +27 -0
  235. package/templates/simple/src/config.ts.ejs +56 -0
  236. package/templates/simple/src/models/_model_.ts.ejs +25 -0
  237. package/templates/simple/src/models/index.ts.ejs +13 -0
  238. package/templates/simple/src/prompts/_model_-prompt.ts.ejs +27 -0
  239. package/templates/simple/src/prompts/index.ts.ejs +18 -0
  240. package/templates/simple/src/server.ts +12 -0
  241. package/templates/simple/test/smoke.test.ts +16 -0
  242. package/templates/simple/tsconfig.json +14 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * <%= projectName %> — shared configuration and startup wiring.
3
+ *
4
+ * Startup phases are wrapped with StartupTracker for clear boundary markers
5
+ * and scoped logging. Each phase gets a scoped child logger.
6
+ */
7
+
8
+ import { loadConfig, readPackageInfo, StartupTracker } from '@mcp-rune/mcp-rune/core'
9
+ import { createPromptCache } from '@mcp-rune/mcp-rune/prompts'
10
+ import { createServer as createServerFactory } from '@mcp-rune/mcp-rune/server'
11
+ <% if (hasHttp && !useStaticTokenAuth) { -%>
12
+ import { _setAdapter, OAuthService, PostgresqlAdapter } from '@mcp-rune/mcp-rune/oauth2'
13
+ <% } -%>
14
+ <% if (usePinoLogger) { -%>
15
+ import { errorTracking, tracing, vectorStorage } from '@mcp-rune/mcp-rune/runtime'
16
+ <% } else { -%>
17
+ import { errorTracking, logger, tracing, vectorStorage } from '@mcp-rune/mcp-rune/runtime'
18
+ <% } -%>
19
+ import { createPgvectorAdapter } from '@mcp-rune/mcp-rune/runtime/vendor/pgvector'
20
+
21
+ import { configSchema } from '../config/schema.js'
22
+ import { closeDatabase, initDatabase } from './db.js'
23
+ <% if (withDomain) { -%>
24
+ import { createDomainRegistry } from './domain/registry.js'
25
+ <% } -%>
26
+ import { MODEL_CLASSES } from './models/index.js'
27
+ <% if (usePinoLogger) { -%>
28
+ import { logger } from './observability/logger.js'
29
+ <% } -%>
30
+ import { getProfile } from './profiles.js'
31
+ import { promptRegistry as basePromptRegistry } from './prompts/index.js'
32
+ import { createToolRegistry } from './tools/index.js'
33
+
34
+ /**
35
+ * Runtime-resolved shape of `configSchema`. Mirrors the schema tree; the
36
+ * library's `loadConfig` returns `Record<string, unknown>` so we narrow at
37
+ * the boundary with a single explicit cast and pay no cost downstream.
38
+ */
39
+ interface AppConfig {
40
+ api: { url: string }
41
+ <% if (hasHttp && !useStaticTokenAuth) { -%>
42
+ oauth: {
43
+ serverUrl: string
44
+ clientId: string
45
+ clientSecret: string
46
+ scopes: string
47
+ }
48
+ <% } -%>
49
+ database: { url: string | undefined }
50
+ analysis: { enabled: boolean }
51
+ <% if (withDomain) { -%>
52
+ domain: { enabled: boolean }
53
+ <% } -%>
54
+ profile: { name: string }
55
+ transport: {
56
+ <% if (hasStdio) { -%>
57
+ local: { accessToken: string | undefined }
58
+ <% } -%>
59
+ <% if (hasHttp) { -%>
60
+ remote: {
61
+ port: number
62
+ baseUrl: string | undefined
63
+ pathPrefix: string
64
+ <% if (useStaticTokenAuth) { -%>
65
+ accessToken: string | undefined
66
+ <% } -%>
67
+ }
68
+ <% } -%>
69
+ }
70
+ http: { corsOrigins: string | undefined; environment: string }
71
+ errorTracking: { sentryDsn: string | undefined }
72
+ tracing: { langfusePublicKey: string | undefined; langfuseSecretKey: string | undefined }
73
+ }
74
+
75
+ const startup = new StartupTracker(logger)
76
+
77
+ const { name: SERVER_NAME, version: SERVER_VERSION } = startup.phase(
78
+ 'identity',
79
+ 'Server identity',
80
+ (log) => {
81
+ const pkg = readPackageInfo(import.meta.url, '../package.json')
82
+ logger.setApp(pkg.name)
83
+ log.debug(`${pkg.name}@${pkg.version}`)
84
+ return pkg
85
+ }
86
+ )
87
+
88
+ export const config = startup.phase('config', 'Load configuration', (log) => {
89
+ const cfg = loadConfig(configSchema)
90
+ log.debug(cfg.toString())
91
+ return cfg as unknown as AppConfig & { toString(): string }
92
+ })
93
+
94
+ const isProduction = config.http.environment === 'production'
95
+
96
+ startup.phase('error-tracking', 'Error tracking', () => {
97
+ // Vendor (Sentry) reads SENTRY_DSN + NODE_ENV from env directly; the
98
+ // public option type here is intentionally narrow.
99
+ errorTracking.initErrorTracking({
100
+ serviceName: SERVER_NAME,
101
+ version: SERVER_VERSION
102
+ })
103
+ })
104
+
105
+ startup.phase('tracing', 'Tracing', () => {
106
+ // Vendor (Langfuse) reads LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY from
107
+ // env directly; the public option type here is intentionally narrow.
108
+ tracing.initTracing({
109
+ serviceName: SERVER_NAME,
110
+ version: SERVER_VERSION
111
+ })
112
+ })
113
+
114
+ startup.phase('database', 'Database', (log) => {
115
+ const pool = initDatabase(config.database.url)
116
+ if (!pool) {
117
+ log.debug('DATABASE_URL not set — database features disabled')
118
+ return
119
+ }
120
+
121
+ <% if (hasHttp && !useStaticTokenAuth) { -%>
122
+ _setAdapter(new PostgresqlAdapter({ pool }))
123
+ log.debug('OAuth token store connected')
124
+
125
+ <% } -%>
126
+ if (config.analysis.enabled) {
127
+ vectorStorage.initVectorStorage({
128
+ adapter: createPgvectorAdapter({ pool }),
129
+ serviceName: SERVER_NAME,
130
+ version: SERVER_VERSION,
131
+ })
132
+ log.debug('Vector storage enabled')
133
+ }
134
+ })
135
+
136
+ <% if (hasHttp && !useStaticTokenAuth) { -%>
137
+ export function createOAuthService({ redirectUri }: { redirectUri: string }): OAuthService {
138
+ return new OAuthService({
139
+ authServerUrl: config.oauth.serverUrl,
140
+ clientId: config.oauth.clientId,
141
+ clientSecret: config.oauth.clientSecret,
142
+ redirectUri,
143
+ scopes: config.oauth.scopes,
144
+ isProduction
145
+ })
146
+ }
147
+
148
+ <% } -%>
149
+ const promptRegistry = startup.phase('prompts', 'Prompts', () => {
150
+ return createPromptCache(basePromptRegistry, { ttl: 5 * 60 * 1000, maxSize: 100 })
151
+ })
152
+
153
+ <% if (withDomain) { -%>
154
+ const domainRegistry = config.domain.enabled
155
+ ? startup.phase('domain', 'Domain intelligence', () => createDomainRegistry())
156
+ : null
157
+ <% } else { -%>
158
+ const domainRegistry = null
159
+ <% } -%>
160
+
161
+ const serverContext = {
162
+ name: '<%= projectNamePascal %>',
163
+ namespace: '<%= projectName %>',
164
+ description: 'MCP server scaffolded with @mcp-rune/create'
165
+ }
166
+
167
+ const profile = getProfile(config.profile.name)
168
+
169
+ const toolRegistry = startup.phase('tools', 'Tools', (log) => {
170
+ log.debug(`Profile: ${config.profile.name}`)
171
+ return createToolRegistry({
172
+ logger,
173
+ models: MODEL_CLASSES,
174
+ promptRegistry,
175
+ serverContext,
176
+ domainRegistry,
177
+ apiUrl: config.api.url,
178
+ profile
179
+ })
180
+ })
181
+
182
+ startup.done()
183
+
184
+ export const mcpConfig = {
185
+ name: SERVER_NAME,
186
+ promptRegistry,
187
+ closeDatabase,
188
+
189
+ createServer({
190
+ sessionId: _sessionId,
191
+ getAccessToken
192
+ }: {
193
+ sessionId: string
194
+ transport: string
195
+ getAccessToken: () => Promise<string>
196
+ }) {
197
+ return createServerFactory({
198
+ name: SERVER_NAME,
199
+ version: SERVER_VERSION,
200
+ sessionId: _sessionId,
201
+ transport: 'stdio',
202
+ toolRegistry,
203
+ promptRegistry,
204
+ getAccessToken
205
+ })
206
+ }
207
+ }
@@ -0,0 +1,35 @@
1
+ import pg from 'pg'
2
+
3
+ type Pool = InstanceType<typeof pg.Pool>
4
+ <% if (usePinoLogger) { -%>
5
+ import { logger } from './observability/logger.js'
6
+ <% } else { -%>
7
+ import { logger } from '@mcp-rune/mcp-rune/runtime'
8
+ <% } -%>
9
+
10
+ let pool: Pool | null = null
11
+
12
+ export function initDatabase(url: string | undefined): Pool | null {
13
+ if (pool) return pool
14
+ if (!url) return null
15
+
16
+ pool = new pg.Pool({
17
+ connectionString: url,
18
+ max: 5,
19
+ idleTimeoutMillis: 30000,
20
+ connectionTimeoutMillis: 5000
21
+ })
22
+ logger.info('Database pool created', { service: '<%= projectName %>-db' })
23
+ return pool
24
+ }
25
+
26
+ export function getPool(): Pool | null {
27
+ return pool
28
+ }
29
+
30
+ export async function closeDatabase(): Promise<void> {
31
+ if (!pool) return
32
+ await pool.end()
33
+ pool = null
34
+ logger.info('Database pool closed', { service: '<%= projectName %>-db' })
35
+ }
@@ -0,0 +1,25 @@
1
+ import type { AttributeDefinition } from '@mcp-rune/mcp-rune/models'
2
+ import { BaseModel } from '@mcp-rune/mcp-rune/models'
3
+
4
+ export class <%= model.namePascal %> extends BaseModel {
5
+ static override description = '<%= model.description || `A ${model.name} record` %>'
6
+ static override api = { endpoint: '<%= model.endpoint || `${model.fileName}s` %>' }
7
+
8
+ static override attributes: Record<string, AttributeDefinition> = {
9
+ <% for (const attr of model.attributes) { -%>
10
+ <%= attr.name %>: {
11
+ type: '<%= attr.type %>',
12
+ <% if (attr.required) { -%>
13
+ required: true,
14
+ <% } -%>
15
+ <% if (attr.description) { -%>
16
+ description: '<%= attr.description %>',
17
+ <% } -%>
18
+ },
19
+ <% } -%>
20
+ }
21
+
22
+ static get attributesConfig(): Record<string, AttributeDefinition> {
23
+ return this.attributes
24
+ }
25
+ }
@@ -0,0 +1,13 @@
1
+ <% for (const m of models) { -%>
2
+ import { <%= m.namePascal %> } from './<%= m.fileName %>.js'
3
+ <% } -%>
4
+
5
+ export const MODEL_CLASSES = {
6
+ <% for (const m of models) { -%>
7
+ <%= m.fileName %>: <%= m.namePascal %>,
8
+ <% } -%>
9
+ }
10
+
11
+ <% for (const m of models) { -%>
12
+ export { <%= m.namePascal %> }
13
+ <% } -%>
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tool exposure profiles.
3
+ *
4
+ * Selected at startup via MCP_PROFILE. Same image, same binary, different surface.
5
+ *
6
+ * - `full` — everything (default).
7
+ * - `chat` — apps enabled; raw-JSON data tools hidden so the LLM doesn't echo
8
+ * records that an app has already rendered.
9
+ * - `agent` — apps disabled; data and action tools enabled for headless callers.
10
+ * - `classify` — only `update_model` is exposed (token-minimal classification).
11
+ *
12
+ * Each profile carries:
13
+ * - `tools` — allowlist of tool names (null = all enabled tools)
14
+ * - `toolsExclude` — denylist applied after allowlist (null = no exclusions)
15
+ * - `apps` — 'enabled' | 'disabled'
16
+ */
17
+
18
+ export interface Profile {
19
+ tools: string[] | null
20
+ toolsExclude: string[] | null
21
+ apps: 'enabled' | 'disabled'
22
+ }
23
+
24
+ export const PROFILES = {
25
+ full: { tools: null, toolsExclude: null, apps: 'enabled' },
26
+ chat: {
27
+ tools: null,
28
+ toolsExclude: ['find_records', 'search_records', 'list_models'],
29
+ apps: 'enabled'
30
+ },
31
+ agent: { tools: null, toolsExclude: null, apps: 'disabled' },
32
+ classify: { tools: ['update_model'], toolsExclude: null, apps: 'disabled' }
33
+ } as const satisfies Record<string, Profile>
34
+
35
+ export type ProfileName = keyof typeof PROFILES
36
+
37
+ export function getProfile(name: string): Profile {
38
+ if (!(name in PROFILES)) {
39
+ throw new Error(
40
+ `Unknown MCP_PROFILE "${name}". Valid: ${Object.keys(PROFILES).join(', ')}`
41
+ )
42
+ }
43
+ return PROFILES[name as ProfileName]
44
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ BasePrompt,
3
+ derivePromptSchema,
4
+ PromptContentBuilder
5
+ } from '@mcp-rune/mcp-rune/prompts'
6
+ import type { FormStrategyType } from '@mcp-rune/mcp-rune/prompts'
7
+
8
+ import { <%= model.namePascal %> } from '../models/<%= model.fileName %>.js'
9
+
10
+ export class <%= model.namePascal %>Prompt extends BasePrompt {
11
+ static override formStrategy: FormStrategyType = 'hybrid'
12
+
13
+ static {
14
+ const schema = derivePromptSchema(<%= model.namePascal %>)
15
+ this.fieldGroups = schema.fieldGroups
16
+ this.fieldDefinitions = schema.fieldDefinitions
17
+ }
18
+
19
+ override get promptContent(): string {
20
+ return PromptContentBuilder.for(<%= model.namePascal %>Prompt, '<%= model.fileName %>')
21
+ .add('# <%= model.namePascal %> guide')
22
+ .standard()
23
+ .toolUsage()
24
+ .attributeReference()
25
+ .build()
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ import { BasePromptRegistry } from '@mcp-rune/mcp-rune/prompts'
2
+
3
+ <% for (const m of models) { -%>
4
+ import { <%= m.namePascal %> } from '../models/<%= m.fileName %>.js'
5
+ <% } -%>
6
+ <% for (const m of models) { -%>
7
+ import { <%= m.namePascal %>Prompt } from './<%= m.fileName %>-prompt.js'
8
+ <% } -%>
9
+
10
+ export const promptRegistry = new BasePromptRegistry()
11
+
12
+ <% for (const m of models) { -%>
13
+ promptRegistry.register('create_<%= m.fileName %>', <%= m.namePascal %>Prompt, {
14
+ description: <%= m.namePascal %>.description,
15
+ required: false,
16
+ model: '<%= m.fileName %>'
17
+ })
18
+ <% } -%>
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Apply mcp-rune database migrations against DATABASE_URL.
4
+ * Tracks applied migrations in a schema_migrations table.
5
+ */
6
+ import { resolve } from 'node:path'
7
+
8
+ import { migrations } from '@mcp-rune/mcp-rune/db/migrations'
9
+ import pg from 'pg'
10
+
11
+ function maskUrl(url: string): string {
12
+ try {
13
+ const u = new URL(url)
14
+ if (u.username || u.password) {
15
+ u.username = '***'
16
+ u.password = ''
17
+ }
18
+ return u.toString().replace(/\/$/, '')
19
+ } catch {
20
+ return '***'
21
+ }
22
+ }
23
+
24
+ function loadEnv(): void {
25
+ try {
26
+ process.loadEnvFile(resolve('.env'))
27
+ } catch {
28
+ /* optional */
29
+ }
30
+ }
31
+
32
+ async function migrate(): Promise<void> {
33
+ loadEnv()
34
+ const url = process.env.DATABASE_URL
35
+ if (!url) {
36
+ console.log('DATABASE_URL not set — skipping')
37
+ return
38
+ }
39
+ console.log(`Connecting to ${maskUrl(url)} …`)
40
+ const pool = new pg.Pool({ connectionString: url })
41
+ try {
42
+ const client = await pool.connect()
43
+ try {
44
+ await client.query(`
45
+ CREATE TABLE IF NOT EXISTS schema_migrations (
46
+ version TEXT PRIMARY KEY,
47
+ name TEXT NOT NULL,
48
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
49
+ )
50
+ `)
51
+ const { rows } = await client.query<{ version: string }>(
52
+ `SELECT version FROM schema_migrations`
53
+ )
54
+ const applied = new Set(rows.map((r) => r.version))
55
+ let count = 0
56
+ for (const m of migrations) {
57
+ if (applied.has(m.version)) {
58
+ console.log(` skip ${m.version}_${m.name}`)
59
+ continue
60
+ }
61
+ console.log(` apply ${m.version}_${m.name}`)
62
+ await client.query('BEGIN')
63
+ try {
64
+ await client.query(m.up)
65
+ await client.query(
66
+ `INSERT INTO schema_migrations (version, name) VALUES ($1, $2)`,
67
+ [m.version, m.name]
68
+ )
69
+ await client.query('COMMIT')
70
+ count++
71
+ } catch (err) {
72
+ await client.query('ROLLBACK')
73
+ throw err
74
+ }
75
+ }
76
+ console.log(
77
+ count === 0 ? 'All migrations already applied.' : `Applied ${count} migration(s).`
78
+ )
79
+ } finally {
80
+ client.release()
81
+ }
82
+ } finally {
83
+ await pool.end()
84
+ }
85
+ }
86
+
87
+ migrate().catch((err: Error) => {
88
+ console.error(`Migration failed: ${err.message}`)
89
+ process.exit(1)
90
+ })
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Tool registry — wraps the library's `ToolRegistry` with an active-profile
3
+ * filter (MCP_PROFILE) so deployments can expose a narrower tool surface
4
+ * than the full set without touching the registry plumbing.
5
+ *
6
+ * The library's `ToolRegistry` already handles capability gating:
7
+ * - `requiresVectorStorage`: skipped when vector storage isn't configured.
8
+ * - `requiresDomainRegistry`: skipped when no `domainRegistry` is passed.
9
+ * - `requiresPromptRegistry`: skipped when no `promptRegistry` is passed.
10
+ * The profile filter layers on top of that with per-deployment allow/deny lists.
11
+ */
12
+
13
+ import type { ApiClient } from '@mcp-rune/mcp-rune/core'
14
+ import type { DomainRegistry } from '@mcp-rune/mcp-rune/domain'
15
+ import type { PromptRegistry } from '@mcp-rune/mcp-rune/prompts'
16
+ import { vectorStorage } from '@mcp-rune/mcp-rune/runtime'
17
+ import type { ModelsRegistry, ServerContext, ToolClassMap } from '@mcp-rune/mcp-rune/tools'
18
+ import {
19
+ ANALYSIS_TOOL_CLASSES,
20
+ DATA_TOOL_CLASSES,
21
+ DOMAIN_TOOL_CLASSES,
22
+ FORM_STRATEGY_TOOL_CLASSES,
23
+ OPERATIONS_TOOL_CLASSES,
24
+ ToolRegistry
25
+ } from '@mcp-rune/mcp-rune/tools'
26
+
27
+ <% if (useFetchClient) { -%>
28
+ import { FetchApiClient } from '../api/fetch-client.js'
29
+ <% } -%>
30
+ <% if (useAxiosClient) { -%>
31
+ import { AxiosApiClient } from '../api/axios-client.js'
32
+ <% } -%>
33
+ <% if (useCustomApiClient) { -%>
34
+ import { CustomApiClient } from '../api/custom-client.js'
35
+ <% } -%>
36
+ <% if (useFlatRestConvention) { -%>
37
+ import { flatRestConvention } from '../conventions/flat-rest-convention.js'
38
+ <% } -%>
39
+ <% if (useCustomConvention) { -%>
40
+ import { customConvention } from '../conventions/custom-convention.js'
41
+ <% } -%>
42
+ <% if (useRansackSearch) { -%>
43
+ import { ransackSearchAdapter } from '../api-extensions/ransack-search-adapter.js'
44
+ <% } -%>
45
+ <% if (useCustomSearch) { -%>
46
+ import { customSearchAdapter } from '../api-extensions/custom-search-adapter.js'
47
+ <% } -%>
48
+
49
+ const ALL_TOOL_CLASSES: ToolClassMap = {
50
+ ...FORM_STRATEGY_TOOL_CLASSES,
51
+ ...DATA_TOOL_CLASSES,
52
+ ...ANALYSIS_TOOL_CLASSES,
53
+ ...OPERATIONS_TOOL_CLASSES,
54
+ ...DOMAIN_TOOL_CLASSES
55
+ }
56
+
57
+ export interface Profile {
58
+ tools?: string[] | null
59
+ toolsExclude?: string[] | null
60
+ }
61
+
62
+ export interface CreateToolRegistryDeps {
63
+ logger: {
64
+ info(message: string, meta?: Record<string, unknown>): void
65
+ warn(message: string, meta?: Record<string, unknown>): void
66
+ error(message: string, meta?: Record<string, unknown>): void
67
+ debug(message: string, meta?: Record<string, unknown>): void
68
+ }
69
+ models: ModelsRegistry
70
+ promptRegistry: PromptRegistry
71
+ serverContext: ServerContext
72
+ domainRegistry: DomainRegistry | null
73
+ apiUrl: string
74
+ profile: Profile
75
+ }
76
+
77
+ function selectToolClasses(profile: Profile): ToolClassMap {
78
+ const all = Object.keys(ALL_TOOL_CLASSES)
79
+ const allow = profile.tools ? new Set(profile.tools) : null
80
+ const deny = profile.toolsExclude ? new Set(profile.toolsExclude) : null
81
+ const selected: ToolClassMap = {}
82
+ for (const name of all) {
83
+ if (allow && !allow.has(name)) continue
84
+ if (deny?.has(name)) continue
85
+ selected[name] = ALL_TOOL_CLASSES[name]!
86
+ }
87
+ return selected
88
+ }
89
+
90
+ export function createToolRegistry(deps: CreateToolRegistryDeps): ToolRegistry {
91
+ const createApiClient = (token: string): ApiClient => {
92
+ <% const conventionExpr = useFlatRestConvention ? 'flatRestConvention' : useCustomConvention ? 'customConvention' : null -%>
93
+ <% if (useFetchClient) { -%>
94
+ return new FetchApiClient({
95
+ baseUrl: deps.apiUrl,
96
+ accessToken: token<% if (conventionExpr) { %>,
97
+ convention: <%= conventionExpr %><% } %>
98
+ })
99
+ <% } else if (useAxiosClient) { -%>
100
+ return new AxiosApiClient({
101
+ baseUrl: deps.apiUrl,
102
+ accessToken: token<% if (conventionExpr) { %>,
103
+ convention: <%= conventionExpr %><% } %>
104
+ })
105
+ <% } else if (useCustomApiClient) { -%>
106
+ return new CustomApiClient({
107
+ baseUrl: deps.apiUrl,
108
+ accessToken: token<% if (conventionExpr) { %>,
109
+ convention: <%= conventionExpr %><% } %>
110
+ })
111
+ <% } else { -%>
112
+ // TODO: replace with your real ApiClient. The Proxy makes a missing
113
+ // implementation fail loudly instead of silently no-oping.
114
+ return new Proxy({} as ApiClient, {
115
+ get() {
116
+ throw new Error(
117
+ `No ApiClient configured (token=${token.slice(0, 4)}…). Wire createApiClient in src/tools/index.ts.`
118
+ )
119
+ }
120
+ })
121
+ <% } -%>
122
+ }
123
+
124
+ return new ToolRegistry({
125
+ toolClasses: selectToolClasses(deps.profile),
126
+ models: deps.models,
127
+ serverContext: deps.serverContext,
128
+ promptRegistry: deps.promptRegistry,
129
+ domainRegistry: deps.domainRegistry ?? undefined,
130
+ vectorStorageEnabled: vectorStorage.isVectorStorageEnabled(),
131
+ createApiClient
132
+ })
133
+ }
@@ -0,0 +1,16 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import { describe, expect, it } from 'vitest'
6
+
7
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
8
+
9
+ describe('scaffold smoke', () => {
10
+ it('package.json declares the design-mandated scripts', async () => {
11
+ const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
12
+ for (const name of ['start', 'test', 'typecheck', 'inspect']) {
13
+ expect(pkg.scripts[name], `missing script: ${name}`).toBeTypeOf('string')
14
+ }
15
+ })
16
+ })
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true
12
+ },
13
+ "include": ["src/**/*.ts", "config/**/*.ts", "test/**/*.ts"]
14
+ }
@@ -0,0 +1,3 @@
1
+ # Token used by the framework's CRUD tools when calling your API backend.
2
+ # For local dev without a real backend, any non-empty value works.
3
+ ACCESS_TOKEN=demo-token
@@ -0,0 +1 @@
1
+ 24
@@ -0,0 +1,40 @@
1
+ # {{projectName}}
2
+
3
+ MCP server scaffolded with [`@mcp-rune/create`](https://github.com/mcp-rune/mcp-rune-cli).
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ cp .env.example .env
9
+ npm install
10
+ npm run start:local
11
+ ```
12
+
13
+ Inspect with the MCP Inspector:
14
+
15
+ ```bash
16
+ npm run inspect
17
+ ```
18
+
19
+ Other scripts:
20
+
21
+ ```bash
22
+ npm run start # same entry as start:local for the simple preset
23
+ npm run test # vitest smoke tests
24
+ npm run typecheck # tsc --noEmit
25
+ ```
26
+
27
+ ## Layout
28
+
29
+ ```
30
+ src/
31
+ ├── server.js StdioServer entry point
32
+ ├── config.js Tool / prompt registry wiring
33
+ ├── models/ Domain model classes
34
+ ├── prompts/ Prompt classes (auto-derived from models)
35
+ └── tools/ Tool registry (CRUD + strategy)
36
+ ```
37
+
38
+ Add a new model by dropping a file under `src/models/` and registering it in `src/models/index.js`. The prompt scaffold is auto-derived.
39
+
40
+ See the [mcp-rune docs](https://github.com/mcp-rune/mcp-rune) for the full framework reference.