@nogataka/smart-edit 0.0.14

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 (186) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +244 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +7 -0
  5. package/dist/devtools/generate_prompt_factory.d.ts +5 -0
  6. package/dist/devtools/generate_prompt_factory.js +114 -0
  7. package/dist/index.d.ts +34 -0
  8. package/dist/index.js +34 -0
  9. package/dist/interprompt/index.d.ts +2 -0
  10. package/dist/interprompt/index.js +1 -0
  11. package/dist/interprompt/jinja_template.d.ts +10 -0
  12. package/dist/interprompt/jinja_template.js +174 -0
  13. package/dist/interprompt/multilang_prompt.d.ts +54 -0
  14. package/dist/interprompt/multilang_prompt.js +302 -0
  15. package/dist/interprompt/prompt_factory.d.ts +16 -0
  16. package/dist/interprompt/prompt_factory.js +189 -0
  17. package/dist/interprompt/util/class_decorators.d.ts +1 -0
  18. package/dist/interprompt/util/class_decorators.js +1 -0
  19. package/dist/interprompt/util/index.d.ts +1 -0
  20. package/dist/interprompt/util/index.js +1 -0
  21. package/dist/serena/agent.d.ts +118 -0
  22. package/dist/serena/agent.js +675 -0
  23. package/dist/serena/agno.d.ts +111 -0
  24. package/dist/serena/agno.js +278 -0
  25. package/dist/serena/analytics.d.ts +24 -0
  26. package/dist/serena/analytics.js +119 -0
  27. package/dist/serena/cli.d.ts +9 -0
  28. package/dist/serena/cli.js +731 -0
  29. package/dist/serena/code_editor.d.ts +42 -0
  30. package/dist/serena/code_editor.js +239 -0
  31. package/dist/serena/config/context_mode.d.ts +41 -0
  32. package/dist/serena/config/context_mode.js +239 -0
  33. package/dist/serena/config/serena_config.d.ts +134 -0
  34. package/dist/serena/config/serena_config.js +718 -0
  35. package/dist/serena/constants.d.ts +18 -0
  36. package/dist/serena/constants.js +27 -0
  37. package/dist/serena/dashboard.d.ts +55 -0
  38. package/dist/serena/dashboard.js +472 -0
  39. package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
  40. package/dist/serena/generated/generated_prompt_factory.js +42 -0
  41. package/dist/serena/gui_log_viewer.d.ts +41 -0
  42. package/dist/serena/gui_log_viewer.js +436 -0
  43. package/dist/serena/mcp.d.ts +118 -0
  44. package/dist/serena/mcp.js +904 -0
  45. package/dist/serena/project.d.ts +62 -0
  46. package/dist/serena/project.js +321 -0
  47. package/dist/serena/prompt_factory.d.ts +20 -0
  48. package/dist/serena/prompt_factory.js +42 -0
  49. package/dist/serena/resources/config/contexts/agent.yml +8 -0
  50. package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
  51. package/dist/serena/resources/config/contexts/codex.yml +27 -0
  52. package/dist/serena/resources/config/contexts/context.template.yml +11 -0
  53. package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
  54. package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
  55. package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
  56. package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
  57. package/dist/serena/resources/config/modes/editing.yml +112 -0
  58. package/dist/serena/resources/config/modes/interactive.yml +11 -0
  59. package/dist/serena/resources/config/modes/mode.template.yml +7 -0
  60. package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
  61. package/dist/serena/resources/config/modes/onboarding.yml +16 -0
  62. package/dist/serena/resources/config/modes/one-shot.yml +15 -0
  63. package/dist/serena/resources/config/modes/planning.yml +15 -0
  64. package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
  65. package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
  66. package/dist/serena/resources/dashboard/dashboard.js +815 -0
  67. package/dist/serena/resources/dashboard/index.html +314 -0
  68. package/dist/serena/resources/dashboard/jquery.min.js +3 -0
  69. package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
  70. package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
  71. package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
  72. package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
  73. package/dist/serena/resources/dashboard/serena-logs.png +0 -0
  74. package/dist/serena/resources/project.template.yml +67 -0
  75. package/dist/serena/resources/serena_config.template.yml +85 -0
  76. package/dist/serena/symbol.d.ts +199 -0
  77. package/dist/serena/symbol.js +616 -0
  78. package/dist/serena/text_utils.d.ts +51 -0
  79. package/dist/serena/text_utils.js +267 -0
  80. package/dist/serena/tools/cmd_tools.d.ts +31 -0
  81. package/dist/serena/tools/cmd_tools.js +48 -0
  82. package/dist/serena/tools/config_tools.d.ts +53 -0
  83. package/dist/serena/tools/config_tools.js +176 -0
  84. package/dist/serena/tools/file_tools.d.ts +231 -0
  85. package/dist/serena/tools/file_tools.js +511 -0
  86. package/dist/serena/tools/index.d.ts +7 -0
  87. package/dist/serena/tools/index.js +7 -0
  88. package/dist/serena/tools/memory_tools.d.ts +60 -0
  89. package/dist/serena/tools/memory_tools.js +135 -0
  90. package/dist/serena/tools/symbol_tools.d.ts +165 -0
  91. package/dist/serena/tools/symbol_tools.js +362 -0
  92. package/dist/serena/tools/tools_base.d.ts +162 -0
  93. package/dist/serena/tools/tools_base.js +378 -0
  94. package/dist/serena/tools/workflow_tools.d.ts +35 -0
  95. package/dist/serena/tools/workflow_tools.js +161 -0
  96. package/dist/serena/util/class_decorators.d.ts +7 -0
  97. package/dist/serena/util/class_decorators.js +37 -0
  98. package/dist/serena/util/exception.d.ts +8 -0
  99. package/dist/serena/util/exception.js +53 -0
  100. package/dist/serena/util/file_system.d.ts +30 -0
  101. package/dist/serena/util/file_system.js +352 -0
  102. package/dist/serena/util/general.d.ts +11 -0
  103. package/dist/serena/util/general.js +42 -0
  104. package/dist/serena/util/git.d.ts +11 -0
  105. package/dist/serena/util/git.js +37 -0
  106. package/dist/serena/util/inspection.d.ts +45 -0
  107. package/dist/serena/util/inspection.js +221 -0
  108. package/dist/serena/util/logging.d.ts +46 -0
  109. package/dist/serena/util/logging.js +205 -0
  110. package/dist/serena/util/shell.d.ts +21 -0
  111. package/dist/serena/util/shell.js +95 -0
  112. package/dist/serena/util/thread.d.ts +23 -0
  113. package/dist/serena/util/thread.js +88 -0
  114. package/dist/serena/version.d.ts +1 -0
  115. package/dist/serena/version.js +23 -0
  116. package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
  117. package/dist/solidlsp/language_servers/autoload.js +25 -0
  118. package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
  119. package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
  120. package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
  121. package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
  122. package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
  123. package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
  124. package/dist/solidlsp/language_servers/common.d.ts +41 -0
  125. package/dist/solidlsp/language_servers/common.js +365 -0
  126. package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
  127. package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
  128. package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
  129. package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
  130. package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
  131. package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
  132. package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
  133. package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
  134. package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
  135. package/dist/solidlsp/language_servers/gopls.js +59 -0
  136. package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
  137. package/dist/solidlsp/language_servers/intelephense.js +121 -0
  138. package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
  139. package/dist/solidlsp/language_servers/jedi_server.js +234 -0
  140. package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
  141. package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
  142. package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
  143. package/dist/solidlsp/language_servers/lua_ls.js +319 -0
  144. package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
  145. package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
  146. package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
  147. package/dist/solidlsp/language_servers/pyright_server.js +180 -0
  148. package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
  149. package/dist/solidlsp/language_servers/r_language_server.js +184 -0
  150. package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
  151. package/dist/solidlsp/language_servers/ruby_common.js +136 -0
  152. package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
  153. package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
  154. package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
  155. package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
  156. package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
  157. package/dist/solidlsp/language_servers/solargraph.js +208 -0
  158. package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
  159. package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
  160. package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
  161. package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
  162. package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
  163. package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
  164. package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
  165. package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
  166. package/dist/solidlsp/language_servers/zls.d.ts +20 -0
  167. package/dist/solidlsp/language_servers/zls.js +254 -0
  168. package/dist/solidlsp/ls.d.ts +197 -0
  169. package/dist/solidlsp/ls.js +507 -0
  170. package/dist/solidlsp/ls_config.d.ts +43 -0
  171. package/dist/solidlsp/ls_config.js +157 -0
  172. package/dist/solidlsp/ls_exceptions.d.ts +5 -0
  173. package/dist/solidlsp/ls_exceptions.js +14 -0
  174. package/dist/solidlsp/ls_handler.d.ts +54 -0
  175. package/dist/solidlsp/ls_handler.js +406 -0
  176. package/dist/solidlsp/ls_request.d.ts +31 -0
  177. package/dist/solidlsp/ls_request.js +42 -0
  178. package/dist/solidlsp/ls_types.d.ts +7 -0
  179. package/dist/solidlsp/ls_types.js +8 -0
  180. package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
  181. package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
  182. package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
  183. package/dist/solidlsp/util/subprocess_util.js +11 -0
  184. package/dist/solidlsp/util/zip.d.ts +25 -0
  185. package/dist/solidlsp/util/zip.js +188 -0
  186. package/package.json +65 -0
@@ -0,0 +1,904 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ErrorCode, McpError, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { createServer } from 'node:http';
7
+ import { URL as NodeURL } from 'node:url';
8
+ import { z } from 'zod';
9
+ import { zodToJsonSchema } from 'zod-to-json-schema';
10
+ import { DEFAULT_CONTEXT, DEFAULT_MODES } from './constants.js';
11
+ import { SerenaAgentContext, SerenaAgentMode } from './config/context_mode.js';
12
+ import { SerenaConfig } from './config/serena_config.js';
13
+ import { createRequire } from 'node:module';
14
+ import { showFatalExceptionSafe } from './util/exception.js';
15
+ import { createSerenaLogger } from './util/logging.js';
16
+ import { SerenaAgent } from './agent.js';
17
+ const require = createRequire(import.meta.url);
18
+ const packageJson = require('../../package.json');
19
+ const { logger: log } = createSerenaLogger({ name: 'serena.mcp' });
20
+ function unwrapZodObject(schema) {
21
+ if (!schema) {
22
+ return undefined;
23
+ }
24
+ if (schema instanceof z.ZodObject) {
25
+ return schema;
26
+ }
27
+ if (schema instanceof z.ZodEffects) {
28
+ const inner = schema._def.schema;
29
+ return unwrapZodObject(inner);
30
+ }
31
+ if (schema instanceof z.ZodDefault) {
32
+ const inner = schema._def.innerType;
33
+ return unwrapZodObject(inner);
34
+ }
35
+ if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) {
36
+ const base = schema.unwrap();
37
+ return unwrapZodObject(base);
38
+ }
39
+ return undefined;
40
+ }
41
+ function toZodRawShape(schema) {
42
+ const objectSchema = unwrapZodObject(schema);
43
+ if (!objectSchema) {
44
+ return undefined;
45
+ }
46
+ return objectSchema.shape;
47
+ }
48
+ function isJsonObject(value) {
49
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
50
+ }
51
+ function deepCloneJsonValue(value) {
52
+ if (Array.isArray(value)) {
53
+ return value.map((item) => deepCloneJsonValue(item));
54
+ }
55
+ if (isJsonObject(value)) {
56
+ const clone = {};
57
+ for (const [key, child] of Object.entries(value)) {
58
+ clone[key] = child === undefined ? undefined : deepCloneJsonValue(child);
59
+ }
60
+ return clone;
61
+ }
62
+ return value;
63
+ }
64
+ function cloneJsonSchema(schema) {
65
+ const structured = globalThis.structuredClone;
66
+ if (typeof structured === 'function') {
67
+ return structured(schema);
68
+ }
69
+ return deepCloneJsonValue(schema);
70
+ }
71
+ function isRecord(value) {
72
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
73
+ }
74
+ function sanitizeJsonSchemaForOpenAi(schema) {
75
+ const cloned = cloneJsonSchema(schema);
76
+ const walk = (node) => {
77
+ if (Array.isArray(node)) {
78
+ return node.map((item) => walk(item));
79
+ }
80
+ if (isJsonObject(node)) {
81
+ const record = node;
82
+ const typeValue = record['type'];
83
+ if (typeof typeValue === 'string') {
84
+ if (typeValue === 'integer') {
85
+ record['type'] = 'number';
86
+ if (record['multipleOf'] === undefined) {
87
+ record['multipleOf'] = 1;
88
+ }
89
+ }
90
+ }
91
+ else if (Array.isArray(typeValue)) {
92
+ const stringEntries = typeValue.filter((entry) => typeof entry === 'string');
93
+ const filteredStrings = stringEntries
94
+ .map((entry) => (entry === 'integer' ? 'number' : entry))
95
+ .filter((entry) => entry !== 'null');
96
+ if (filteredStrings.length === 0) {
97
+ record['type'] = 'object';
98
+ }
99
+ else if (filteredStrings.length === 1) {
100
+ record['type'] = filteredStrings[0];
101
+ }
102
+ else {
103
+ record['type'] = filteredStrings;
104
+ }
105
+ if (stringEntries.includes('integer') || filteredStrings.includes('number')) {
106
+ record['multipleOf'] = record['multipleOf'] ?? 1;
107
+ }
108
+ }
109
+ const enumValue = record['enum'];
110
+ if (Array.isArray(enumValue)) {
111
+ const values = enumValue;
112
+ if (values.length > 0 && values.every((value) => typeof value === 'number' && Number.isInteger(value))) {
113
+ record['type'] = record['type'] ?? 'number';
114
+ record['multipleOf'] = record['multipleOf'] ?? 1;
115
+ }
116
+ }
117
+ for (const key of ['oneOf', 'anyOf']) {
118
+ const value = record[key];
119
+ if (Array.isArray(value)) {
120
+ const sanitized = value.map((entry) => walk(entry));
121
+ if (sanitized.length === 2) {
122
+ const types = sanitized.map((entry) => (isJsonObject(entry) ? entry['type'] : undefined));
123
+ if (types.includes('null')) {
124
+ const nonNullIndex = types.findIndex((type) => type !== 'null');
125
+ const nonNull = nonNullIndex >= 0 ? sanitized[nonNullIndex] : undefined;
126
+ if (isJsonObject(nonNull)) {
127
+ Object.assign(record, nonNull);
128
+ delete record[key];
129
+ continue;
130
+ }
131
+ }
132
+ }
133
+ try {
134
+ const serialized = sanitized.map((entry) => JSON.stringify(entry));
135
+ const unique = new Set(serialized);
136
+ if (unique.size === 1) {
137
+ const only = sanitized[0];
138
+ if (isJsonObject(only)) {
139
+ Object.assign(record, only);
140
+ delete record[key];
141
+ continue;
142
+ }
143
+ }
144
+ }
145
+ catch {
146
+ // Keep original structure if serialization fails.
147
+ }
148
+ record[key] = sanitized;
149
+ }
150
+ }
151
+ for (const [key, value] of Object.entries(record)) {
152
+ if (value === undefined) {
153
+ continue;
154
+ }
155
+ if (Array.isArray(value) || isJsonObject(value)) {
156
+ record[key] = walk(value);
157
+ }
158
+ }
159
+ return record;
160
+ }
161
+ return node;
162
+ };
163
+ return walk(cloned);
164
+ }
165
+ function maybeCreateJsonSchema(schema, options) {
166
+ const objectSchema = unwrapZodObject(schema);
167
+ if (!objectSchema) {
168
+ return undefined;
169
+ }
170
+ const jsonSchema = zodToJsonSchema(objectSchema, {
171
+ name: options.name,
172
+ target: 'jsonSchema7',
173
+ $refStrategy: 'none'
174
+ });
175
+ if (!jsonSchema || typeof jsonSchema !== 'object') {
176
+ return undefined;
177
+ }
178
+ if (options.sanitizeForOpenAiTools) {
179
+ return sanitizeJsonSchemaForOpenAi(jsonSchema);
180
+ }
181
+ return jsonSchema;
182
+ }
183
+ function createDefaultServerInfo() {
184
+ const version = typeof packageJson.version === 'string' ? packageJson.version : '0.0.0';
185
+ return {
186
+ name: 'FastMCP',
187
+ version
188
+ };
189
+ }
190
+ /*
191
+ * NOTE: The MCP SDK currently ships without fully typed ESM re-exports, which
192
+ * causes TypeScript to treat several helper methods as `any`. We perform
193
+ * runtime guards before calling into the SDK and suppress the lint warnings
194
+ * locally to avoid scattering disable directives at call sites.
195
+ */
196
+ function registerToolWithServer(server, options) {
197
+ const toolMethod = server.tool.bind(server);
198
+ const registered = options.annotations && Object.keys(options.annotations).length > 0
199
+ ? toolMethod(options.name, options.description, options.inputSchema, options.annotations, options.callback)
200
+ : toolMethod(options.name, options.description, options.inputSchema, options.callback);
201
+ if (options.outputSchema) {
202
+ registered.update({ outputSchema: options.outputSchema });
203
+ }
204
+ const serverWithTracking = server;
205
+ serverWithTracking.__serenaRegisteredTools ??= new Map();
206
+ const annotations = options.annotations && Object.keys(options.annotations).length > 0 ? options.annotations : undefined;
207
+ serverWithTracking.__serenaRegisteredTools.set(options.name, {
208
+ description: options.description,
209
+ annotations,
210
+ _meta: undefined,
211
+ callback: options.callback
212
+ });
213
+ return registered;
214
+ }
215
+ export class SerenaMCPFactory {
216
+ context;
217
+ project;
218
+ agent = null;
219
+ requestContext = null;
220
+ constructor(context = DEFAULT_CONTEXT, project = null) {
221
+ this.context = SerenaAgentContext.load(context);
222
+ this.project = project ?? null;
223
+ }
224
+ getAgent() {
225
+ if (!this.agent) {
226
+ throw new Error('Serena agent has not been instantiated yet.');
227
+ }
228
+ return this.agent;
229
+ }
230
+ setAgent(agent) {
231
+ this.agent = agent;
232
+ this.requestContext = { agent };
233
+ }
234
+ getRequestContext() {
235
+ if (!this.requestContext) {
236
+ throw new Error('Request context not initialized.');
237
+ }
238
+ return this.requestContext;
239
+ }
240
+ isOpenAiCompatibleContext() {
241
+ return ['chatgpt', 'codex', 'oaicompat-agent'].includes(this.context.name);
242
+ }
243
+ registerDefaultCapabilities(mcpServer) {
244
+ const server = mcpServer.server;
245
+ server.registerCapabilities({
246
+ experimental: {},
247
+ prompts: {
248
+ listChanged: false
249
+ },
250
+ resources: {
251
+ subscribe: false,
252
+ listChanged: false
253
+ }
254
+ });
255
+ const agent = this.getAgent();
256
+ const promptFactory = agent.promptFactory;
257
+ const memoriesManager = agent.memoriesManager;
258
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
259
+ prompts: this.buildPromptMetadata(promptFactory)
260
+ }));
261
+ server.setRequestHandler(GetPromptRequestSchema, (request) => this.buildPromptResult(promptFactory, request.params.name, request.params.arguments ?? {}));
262
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
263
+ resources: await this.buildResourceList(memoriesManager)
264
+ }));
265
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
266
+ resourceTemplates: this.buildResourceTemplates(memoriesManager)
267
+ }));
268
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => this.readResource(memoriesManager, request.params.uri));
269
+ }
270
+ buildPromptMetadata(promptFactory) {
271
+ const names = promptFactory.listPromptTemplateNames();
272
+ return names.map((name) => {
273
+ const params = promptFactory.getPromptTemplateParameters(name);
274
+ return {
275
+ name,
276
+ description: this.describePromptTemplate(name),
277
+ arguments: params.length === 0
278
+ ? undefined
279
+ : params.map((param) => ({
280
+ name: param,
281
+ required: true
282
+ }))
283
+ };
284
+ });
285
+ }
286
+ buildPromptResult(promptFactory, promptName, rawArgs) {
287
+ if (!promptFactory.hasPromptTemplate(promptName)) {
288
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${promptName} not found`);
289
+ }
290
+ const requiredParams = promptFactory.getPromptTemplateParameters(promptName);
291
+ const missingParams = requiredParams.filter((param) => !(param in rawArgs));
292
+ if (missingParams.length > 0) {
293
+ throw new McpError(ErrorCode.InvalidParams, `Missing required arguments for prompt ${promptName}: ${missingParams.join(', ')}`);
294
+ }
295
+ const rendered = promptFactory.renderPrompt(promptName, rawArgs);
296
+ return {
297
+ description: this.describePromptTemplate(promptName),
298
+ messages: [
299
+ {
300
+ role: 'assistant',
301
+ content: [
302
+ {
303
+ type: 'text',
304
+ text: rendered
305
+ }
306
+ ]
307
+ }
308
+ ]
309
+ };
310
+ }
311
+ describePromptTemplate(name) {
312
+ const readable = name.replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').trim();
313
+ if (!readable) {
314
+ return undefined;
315
+ }
316
+ return readable.charAt(0).toUpperCase() + readable.slice(1);
317
+ }
318
+ async buildResourceList(manager) {
319
+ if (!manager) {
320
+ return [];
321
+ }
322
+ const names = await this.listMemories(manager);
323
+ return names.map((name) => ({
324
+ uri: this.formatMemoryUri(name),
325
+ name,
326
+ description: 'Serena project memory entry',
327
+ mimeType: 'text/markdown'
328
+ }));
329
+ }
330
+ buildResourceTemplates(manager) {
331
+ if (!manager) {
332
+ return [];
333
+ }
334
+ return [
335
+ {
336
+ name: 'serena-memory',
337
+ uriTemplate: 'serena://memory/{name}',
338
+ description: 'Template for accessing Serena project memory entries',
339
+ mimeType: 'text/markdown'
340
+ }
341
+ ];
342
+ }
343
+ async readResource(manager, uri) {
344
+ const memoryName = this.parseMemoryUri(uri);
345
+ if (!memoryName) {
346
+ throw new McpError(ErrorCode.InvalidParams, `Unsupported resource URI: ${uri}`);
347
+ }
348
+ if (!manager) {
349
+ throw new McpError(ErrorCode.InvalidParams, 'Memory store is unavailable');
350
+ }
351
+ const content = await this.loadMemory(manager, memoryName);
352
+ return {
353
+ contents: [
354
+ {
355
+ type: 'text',
356
+ text: content,
357
+ mimeType: 'text/markdown'
358
+ }
359
+ ]
360
+ };
361
+ }
362
+ parseMemoryUri(uri) {
363
+ if (!uri.toLowerCase().startsWith('serena://memory/')) {
364
+ return null;
365
+ }
366
+ const suffix = uri.slice('serena://memory/'.length);
367
+ if (!suffix) {
368
+ return null;
369
+ }
370
+ try {
371
+ return decodeURIComponent(suffix);
372
+ }
373
+ catch {
374
+ return suffix;
375
+ }
376
+ }
377
+ formatMemoryUri(name) {
378
+ return `serena://memory/${encodeURIComponent(name)}`;
379
+ }
380
+ async listMemories(manager) {
381
+ const listFn = this.bindManagerFunction(manager, ['listMemories', 'list_memories']);
382
+ const raw = await Promise.resolve(listFn());
383
+ if (!raw) {
384
+ return [];
385
+ }
386
+ if (Array.isArray(raw)) {
387
+ return raw.map((entry) => this.stringifyUnknown(entry));
388
+ }
389
+ if (this.isIterable(raw)) {
390
+ const result = [];
391
+ for (const entry of raw) {
392
+ result.push(this.stringifyUnknown(entry));
393
+ }
394
+ return result;
395
+ }
396
+ return [this.stringifyUnknown(raw)];
397
+ }
398
+ async loadMemory(manager, name) {
399
+ const loadFn = this.bindManagerFunction(manager, ['loadMemory', 'load_memory']);
400
+ const raw = await Promise.resolve(loadFn(name));
401
+ const text = this.stringifyUnknown(raw);
402
+ return text;
403
+ }
404
+ bindManagerFunction(manager, candidateNames) {
405
+ for (const candidate of candidateNames) {
406
+ const fn = Reflect.get(manager, candidate);
407
+ if (typeof fn === 'function') {
408
+ return (...fnArgs) => fn.apply(manager, fnArgs);
409
+ }
410
+ }
411
+ throw new Error(`Memories manager does not implement any of: ${candidateNames.join(', ')}`);
412
+ }
413
+ stringifyUnknown(value) {
414
+ if (value === undefined || value === null) {
415
+ return '';
416
+ }
417
+ if (typeof value === 'string') {
418
+ return value;
419
+ }
420
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
421
+ return value.toString();
422
+ }
423
+ if (typeof value === 'symbol') {
424
+ return value.toString();
425
+ }
426
+ if (typeof value === 'function') {
427
+ return '[function]';
428
+ }
429
+ if (typeof value === 'object') {
430
+ try {
431
+ return JSON.stringify(value);
432
+ }
433
+ catch {
434
+ return Object.prototype.toString.call(value);
435
+ }
436
+ }
437
+ return '[unrecognized]';
438
+ }
439
+ isIterable(value) {
440
+ return typeof value === 'object' && value !== null && Symbol.iterator in value;
441
+ }
442
+ registerTools(server, openAiToolCompatible) {
443
+ const tools = Array.from(this.iterTools());
444
+ for (const tool of tools) {
445
+ this.registerTool(server, tool, openAiToolCompatible);
446
+ }
447
+ log.info(`Registered ${tools.length} MCP tools: ${tools.map((tool) => tool.getName()).join(', ')}`);
448
+ }
449
+ registerTool(server, tool, openAiToolCompatible) {
450
+ const toolName = tool.getName();
451
+ const metadata = tool.getApplyFnMetadata();
452
+ const descriptionOverride = this.context.toolDescriptionOverrides[toolName];
453
+ let description = descriptionOverride ?? metadata.description ?? '';
454
+ description = description.trim();
455
+ if (description.length > 0 && !description.endsWith('.')) {
456
+ description += '.';
457
+ }
458
+ const inputSchemaShape = toZodRawShape(metadata.inputSchema);
459
+ if (!inputSchemaShape) {
460
+ throw new Error(`Tool ${toolName} must expose a Zod object input schema.`);
461
+ }
462
+ const outputSchemaShape = toZodRawShape(metadata.outputSchema);
463
+ const inputJsonSchema = openAiToolCompatible
464
+ ? maybeCreateJsonSchema(metadata.inputSchema, {
465
+ name: `${toolName}Input`,
466
+ sanitizeForOpenAiTools: true
467
+ })
468
+ : undefined;
469
+ const outputJsonSchema = metadata.outputSchema
470
+ ? maybeCreateJsonSchema(metadata.outputSchema, {
471
+ name: `${toolName}Output`,
472
+ sanitizeForOpenAiTools: openAiToolCompatible
473
+ })
474
+ : undefined;
475
+ const annotations = openAiToolCompatible
476
+ ? {
477
+ 'serena/openaiToolCompatible': true,
478
+ ...(inputJsonSchema ? { 'serena/openaiToolInputSchema': inputJsonSchema } : {}),
479
+ ...(outputJsonSchema ? { 'serena/openaiToolOutputSchema': outputJsonSchema } : {})
480
+ }
481
+ : undefined;
482
+ const callback = async (args, extra) => {
483
+ try {
484
+ const normalizedArgs = args;
485
+ log.debug('MCP invoking tool', {
486
+ tool: toolName,
487
+ normalizedArgs,
488
+ hasAgent: this.agent !== null
489
+ });
490
+ const result = await tool.applyEx(normalizedArgs, { logCall: true, catchExceptions: true });
491
+ log.debug('Tool result before structured output handling', {
492
+ tool: toolName,
493
+ mode: metadata.structuredOutput ? 'structured' : 'text',
494
+ result
495
+ });
496
+ if (metadata.structuredOutput && outputJsonSchema && metadata.outputSchema) {
497
+ let parsed;
498
+ try {
499
+ parsed = JSON.parse(result);
500
+ }
501
+ catch (error) {
502
+ const message = error instanceof Error ? error.message : String(error);
503
+ return {
504
+ content: [
505
+ {
506
+ type: 'text',
507
+ text: `Failed to parse structured output from tool ${toolName}: ${message}`
508
+ }
509
+ ],
510
+ isError: true
511
+ };
512
+ }
513
+ if (!isRecord(parsed)) {
514
+ return {
515
+ content: [
516
+ {
517
+ type: 'text',
518
+ text: `Tool ${toolName} must return a JSON object when structuredOutput is enabled.`
519
+ }
520
+ ],
521
+ isError: true
522
+ };
523
+ }
524
+ return {
525
+ content: [],
526
+ structuredContent: parsed
527
+ };
528
+ }
529
+ return {
530
+ content: [
531
+ {
532
+ type: 'text',
533
+ text: result
534
+ }
535
+ ]
536
+ };
537
+ }
538
+ catch (error) {
539
+ log.error('MCP tool execution threw an unexpected error', { err: error, tool: toolName, extra });
540
+ throw error;
541
+ }
542
+ };
543
+ const registered = registerToolWithServer(server, {
544
+ name: toolName,
545
+ description,
546
+ inputSchema: inputSchemaShape,
547
+ annotations,
548
+ callback,
549
+ outputSchema: outputSchemaShape
550
+ });
551
+ return registered;
552
+ }
553
+ createMcpServer(options = {}) {
554
+ try {
555
+ const config = SerenaConfig.fromConfigFile();
556
+ if (options.enableWebDashboard !== undefined && options.enableWebDashboard !== null) {
557
+ config.webDashboard = options.enableWebDashboard;
558
+ }
559
+ if (options.enableGuiLogWindow !== undefined && options.enableGuiLogWindow !== null) {
560
+ config.guiLogWindowEnabled = options.enableGuiLogWindow;
561
+ }
562
+ if (options.logLevel) {
563
+ const normalized = options.logLevel.toUpperCase();
564
+ const level = LOG_LEVEL_MAP[normalized] ?? LOG_LEVEL_MAP.INFO;
565
+ config.logLevel = level;
566
+ }
567
+ if (options.traceLspCommunication !== undefined && options.traceLspCommunication !== null) {
568
+ config.traceLspCommunication = options.traceLspCommunication;
569
+ }
570
+ if (options.toolTimeout !== undefined && options.toolTimeout !== null) {
571
+ config.toolTimeout = options.toolTimeout;
572
+ }
573
+ const modesInput = options.modes ? Array.from(options.modes) : Array.from(DEFAULT_MODES);
574
+ const modes = modesInput.map((mode) => SerenaAgentMode.load(mode));
575
+ const agent = this.instantiateAgent({
576
+ serenaConfig: config,
577
+ modes
578
+ });
579
+ this.setAgent(agent);
580
+ const instructions = options.instructionsOverride !== undefined
581
+ ? options.instructionsOverride ?? ''
582
+ : this.getInitialInstructions();
583
+ const serverInfo = options.serverInfo ?? createDefaultServerInfo();
584
+ const serverOptions = {
585
+ ...(options.serverOptions ?? {}),
586
+ instructions
587
+ };
588
+ const mcpServer = new McpServer(serverInfo, serverOptions);
589
+ this.registerTools(mcpServer, this.isOpenAiCompatibleContext());
590
+ this.registerDefaultCapabilities(mcpServer);
591
+ return mcpServer;
592
+ }
593
+ catch (error) {
594
+ void showFatalExceptionSafe(error);
595
+ throw error;
596
+ }
597
+ }
598
+ getLogger() {
599
+ return log;
600
+ }
601
+ }
602
+ const LOG_LEVEL_MAP = {
603
+ DEBUG: 10,
604
+ INFO: 20,
605
+ WARNING: 30,
606
+ ERROR: 40,
607
+ CRITICAL: 50
608
+ };
609
+ export class SerenaMCPFactorySingleProcess extends SerenaMCPFactory {
610
+ memoryLogHandler;
611
+ agentFactory;
612
+ cachedTools = null;
613
+ constructor(options = {}) {
614
+ super(options.context, options.project ?? null);
615
+ this.memoryLogHandler = options.memoryLogHandler ?? null;
616
+ this.agentFactory =
617
+ options.agentFactory ??
618
+ ((factoryOptions) => new SerenaAgent({
619
+ project: factoryOptions.project,
620
+ serenaConfig: factoryOptions.serenaConfig,
621
+ context: factoryOptions.context,
622
+ modes: factoryOptions.modes,
623
+ memoryLogHandler: factoryOptions.memoryLogHandler ?? undefined
624
+ }));
625
+ }
626
+ instantiateAgent({ serenaConfig, modes }) {
627
+ if (!this.agentFactory) {
628
+ throw new Error('SerenaAgent factory not provided. Inject a factory via SerenaMCPFactorySingleProcessOptions.agentFactory once the agent implementation is available.');
629
+ }
630
+ const agent = this.agentFactory({
631
+ project: this.project,
632
+ serenaConfig,
633
+ context: this.context,
634
+ modes,
635
+ memoryLogHandler: this.memoryLogHandler
636
+ });
637
+ const toolProvider = agent;
638
+ if (typeof toolProvider.getExposedToolInstances === 'function') {
639
+ this.cachedTools = Array.from(toolProvider.getExposedToolInstances());
640
+ }
641
+ else if (typeof toolProvider.get_exposed_tool_instances === 'function') {
642
+ this.cachedTools = Array.from(toolProvider.get_exposed_tool_instances());
643
+ }
644
+ else {
645
+ this.cachedTools = null;
646
+ }
647
+ return agent;
648
+ }
649
+ iterTools() {
650
+ if (this.cachedTools !== null) {
651
+ return this.cachedTools;
652
+ }
653
+ const agent = this.getAgent();
654
+ if (typeof agent.getExposedToolInstances === 'function') {
655
+ const tools = Array.from(agent.getExposedToolInstances());
656
+ this.cachedTools = tools;
657
+ return tools;
658
+ }
659
+ if (typeof agent.get_exposed_tool_instances === 'function') {
660
+ const tools = Array.from(agent.get_exposed_tool_instances());
661
+ this.cachedTools = tools;
662
+ return tools;
663
+ }
664
+ throw new Error('Agent does not expose tool discovery methods.');
665
+ }
666
+ getInitialInstructions() {
667
+ const agent = this.getAgent();
668
+ if (typeof agent.createSystemPrompt === 'function') {
669
+ const prompt = agent.createSystemPrompt();
670
+ if (typeof prompt === 'string' && prompt.length > 0) {
671
+ return prompt;
672
+ }
673
+ }
674
+ if (typeof agent.create_system_prompt === 'function') {
675
+ const prompt = agent.create_system_prompt();
676
+ if (typeof prompt === 'string' && prompt.length > 0) {
677
+ return prompt;
678
+ }
679
+ }
680
+ throw new Error('Agent did not provide a system prompt.');
681
+ }
682
+ }
683
+ const DEFAULT_HTTP_PATH = '/mcp';
684
+ function normalizeHttpPath(path) {
685
+ if (path === undefined || path === null || path === '') {
686
+ return DEFAULT_HTTP_PATH;
687
+ }
688
+ return path.startsWith('/') ? path : `/${path}`;
689
+ }
690
+ function resolveRequestPath(req, defaultHost) {
691
+ if (!req.url) {
692
+ return null;
693
+ }
694
+ try {
695
+ const hostHeader = req.headers.host ?? defaultHost;
696
+ const parsed = new NodeURL(req.url, `http://${hostHeader}`);
697
+ return parsed.pathname;
698
+ }
699
+ catch {
700
+ return null;
701
+ }
702
+ }
703
+ function respondWithJson(res, statusCode, body) {
704
+ if (res.headersSent) {
705
+ return;
706
+ }
707
+ res.writeHead(statusCode, {
708
+ 'Content-Type': 'application/json; charset=utf-8',
709
+ 'Cache-Control': 'no-store'
710
+ });
711
+ res.end(JSON.stringify(body));
712
+ }
713
+ export function createSerenaHttpRequestHandler({ transport, path, defaultHost }) {
714
+ const normalizedPath = normalizeHttpPath(path);
715
+ const fallbackHost = defaultHost ?? '127.0.0.1';
716
+ return async (req, res) => {
717
+ const requestPath = resolveRequestPath(req, fallbackHost);
718
+ if (requestPath !== normalizedPath) {
719
+ respondWithJson(res, 404, {
720
+ jsonrpc: '2.0',
721
+ error: {
722
+ code: -32601,
723
+ message: 'Not Found'
724
+ },
725
+ id: null
726
+ });
727
+ return;
728
+ }
729
+ try {
730
+ await transport.handleRequest(req, res);
731
+ }
732
+ catch (error) {
733
+ log.error('Unhandled error while processing HTTP MCP request', { err: error });
734
+ if (!res.headersSent) {
735
+ respondWithJson(res, 500, {
736
+ jsonrpc: '2.0',
737
+ error: {
738
+ code: -32000,
739
+ message: 'Internal Server Error',
740
+ data: error instanceof Error ? error.message : String(error)
741
+ },
742
+ id: null
743
+ });
744
+ }
745
+ else {
746
+ try {
747
+ res.end();
748
+ }
749
+ catch {
750
+ // ignore secondary failures when the connection is already broken
751
+ }
752
+ }
753
+ }
754
+ };
755
+ }
756
+ async function listenHttpServer(server, port, host) {
757
+ await new Promise((resolve, reject) => {
758
+ const onError = (error) => {
759
+ server.off('listening', onListening);
760
+ reject(error);
761
+ };
762
+ const onListening = () => {
763
+ server.off('error', onError);
764
+ resolve();
765
+ };
766
+ server.once('error', onError);
767
+ server.listen(port, host, onListening);
768
+ });
769
+ }
770
+ async function closeHttpServer(server) {
771
+ await new Promise((resolve, reject) => {
772
+ server.close((error) => {
773
+ if (error) {
774
+ reject(error);
775
+ return;
776
+ }
777
+ resolve();
778
+ });
779
+ });
780
+ }
781
+ function deriveServerUrl(address, path, fallbackHost) {
782
+ if (!address) {
783
+ throw new Error('Failed to determine HTTP server address.');
784
+ }
785
+ if (typeof address === 'string') {
786
+ const normalized = address.endsWith('/') ? address.slice(0, -1) : address;
787
+ if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
788
+ return new NodeURL(`${normalized}${path}`);
789
+ }
790
+ return new NodeURL(`http://${fallbackHost}${path}`);
791
+ }
792
+ const host = address.family === 'IPv6' ? `[${address.address}]` : address.address;
793
+ const resolvedHost = host === '::' || host === '0.0.0.0' ? fallbackHost : host;
794
+ return new NodeURL(`http://${resolvedHost}:${address.port}${path}`);
795
+ }
796
+ export async function createSerenaHttpServer(factory, options = {}) {
797
+ const host = options.host ?? '127.0.0.1';
798
+ const port = options.port ?? 0;
799
+ const path = normalizeHttpPath(options.path);
800
+ const transportOptions = options.transportOptions ?? {};
801
+ const sessionIdGenerator = options.sessionIdGenerator === null
802
+ ? undefined
803
+ : options.sessionIdGenerator ?? transportOptions.sessionIdGenerator ?? (() => randomUUID());
804
+ const httpTransport = new StreamableHTTPServerTransport({
805
+ ...transportOptions,
806
+ sessionIdGenerator
807
+ });
808
+ const mcpServer = factory.createMcpServer(options);
809
+ await mcpServer.connect(httpTransport);
810
+ const handler = createSerenaHttpRequestHandler({
811
+ transport: httpTransport,
812
+ path,
813
+ defaultHost: host
814
+ });
815
+ const httpServer = createServer((req, res) => {
816
+ void handler(req, res);
817
+ });
818
+ await listenHttpServer(httpServer, port, host);
819
+ const addressInfo = httpServer.address();
820
+ const url = deriveServerUrl(addressInfo, path, host);
821
+ return {
822
+ httpServer,
823
+ transport: httpTransport,
824
+ mcpServer,
825
+ url,
826
+ async close() {
827
+ await Promise.allSettled([httpTransport.close(), mcpServer.close()]);
828
+ await closeHttpServer(httpServer);
829
+ }
830
+ };
831
+ }
832
+ class SerenaStdioServerTransport extends StdioServerTransport {
833
+ stdin;
834
+ handleStreamEnd;
835
+ handleStreamClose;
836
+ closePromise = null;
837
+ constructor(stdin = process.stdin, stdout = process.stdout) {
838
+ super(stdin, stdout);
839
+ this.stdin = stdin;
840
+ this.handleStreamEnd = () => {
841
+ void this.ensureClosed().catch((error) => {
842
+ this.reportError(error);
843
+ });
844
+ };
845
+ this.handleStreamClose = () => {
846
+ void this.ensureClosed().catch((error) => {
847
+ this.reportError(error);
848
+ });
849
+ };
850
+ this.stdin.on('end', this.handleStreamEnd);
851
+ this.stdin.on('close', this.handleStreamClose);
852
+ }
853
+ ensureClosed() {
854
+ if (!this.closePromise) {
855
+ this.stdin.off('end', this.handleStreamEnd);
856
+ this.stdin.off('close', this.handleStreamClose);
857
+ this.closePromise = super.close();
858
+ }
859
+ return this.closePromise;
860
+ }
861
+ async close() {
862
+ await this.ensureClosed();
863
+ }
864
+ reportError(error) {
865
+ if (error instanceof Error) {
866
+ this.onerror?.(error);
867
+ return;
868
+ }
869
+ let message;
870
+ if (typeof error === 'string') {
871
+ message = error;
872
+ }
873
+ else {
874
+ try {
875
+ message = JSON.stringify(error);
876
+ }
877
+ catch {
878
+ message = '[unknown error]';
879
+ }
880
+ }
881
+ this.onerror?.(new Error(message));
882
+ }
883
+ }
884
+ export async function createSerenaStdioServer(factory, options = {}) {
885
+ const transport = new SerenaStdioServerTransport();
886
+ const mcpServer = factory.createMcpServer(options);
887
+ await mcpServer.connect(transport);
888
+ return {
889
+ transport,
890
+ mcpServer,
891
+ async close() {
892
+ await Promise.allSettled([transport.close(), mcpServer.close()]);
893
+ }
894
+ };
895
+ }
896
+ export async function createSerenaGrpcServer(_factory, _options = {}) {
897
+ try {
898
+ await import('@grpc/grpc-js');
899
+ }
900
+ catch (error) {
901
+ throw new Error('gRPCトランスポートPoCは未実装です。@grpc/grpc-js を導入後に createSerenaGrpcServer を拡張してください。', { cause: error });
902
+ }
903
+ throw new Error('gRPCトランスポートPoCは未実装です。HTTPモードのPoCは createSerenaHttpServer を利用してください。');
904
+ }