@takara-ai/miru-code 1.0.3 → 1.0.4
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/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-php_only.wasm +0 -0
- package/package.json +9 -8
- package/src/mcp/runtime.ts +241 -0
- package/src/mcp/serve.ts +2 -2
- package/src/mcp/server.ts +3 -3
- package/src/mcp/stdio.ts +111 -0
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takara-ai/miru-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,21 +61,20 @@
|
|
|
61
61
|
"benchmark:quantization": "bun run scripts/benchmark-quantization.ts"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
65
64
|
"ignore": "^7.0.5",
|
|
66
65
|
"web-tree-sitter": "^0.26.9",
|
|
67
66
|
"zod": "^4.4.3"
|
|
68
67
|
},
|
|
69
68
|
"devDependencies": {
|
|
70
|
-
"@biomejs/biome": "^2.
|
|
71
|
-
"@types/bun": "
|
|
69
|
+
"@biomejs/biome": "^2.5.1",
|
|
70
|
+
"@types/bun": "^1.3.14",
|
|
72
71
|
"tree-sitter-bash": "^0.25.1",
|
|
73
72
|
"tree-sitter-c": "^0.24.1",
|
|
74
|
-
"tree-sitter-c-sharp": "^0.23.
|
|
73
|
+
"tree-sitter-c-sharp": "^0.23.5",
|
|
75
74
|
"tree-sitter-cpp": "^0.23.4",
|
|
76
75
|
"tree-sitter-css": "^0.25.0",
|
|
77
76
|
"tree-sitter-dart": "^1.0.0",
|
|
78
|
-
"tree-sitter-elixir": "^0.3.
|
|
77
|
+
"tree-sitter-elixir": "^0.3.5",
|
|
79
78
|
"tree-sitter-embedded-template": "^0.25.0",
|
|
80
79
|
"tree-sitter-go": "^0.25.0",
|
|
81
80
|
"tree-sitter-haskell": "^0.23.1",
|
|
@@ -84,13 +83,15 @@
|
|
|
84
83
|
"tree-sitter-javascript": "^0.25.0",
|
|
85
84
|
"tree-sitter-json": "^0.24.8",
|
|
86
85
|
"tree-sitter-ocaml": "^0.24.2",
|
|
87
|
-
"tree-sitter-php": "^0.
|
|
86
|
+
"tree-sitter-php": "^0.24.2",
|
|
88
87
|
"tree-sitter-python": "^0.25.0",
|
|
89
88
|
"tree-sitter-ruby": "^0.23.1",
|
|
90
89
|
"tree-sitter-rust": "^0.24.0",
|
|
91
90
|
"tree-sitter-scala": "^0.24.0",
|
|
92
91
|
"tree-sitter-solidity": "^1.2.13",
|
|
93
92
|
"tree-sitter-typescript": "^0.23.2",
|
|
94
|
-
"typescript": "^
|
|
93
|
+
"typescript": "^6.0.3",
|
|
94
|
+
"ajv": "^8.17.1",
|
|
95
|
+
"ajv-formats": "^3.0.1"
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import {
|
|
3
|
+
type JsonRpcError,
|
|
4
|
+
type JsonRpcId,
|
|
5
|
+
type JsonRpcMessage,
|
|
6
|
+
type JsonRpcRequest,
|
|
7
|
+
type JsonRpcResponse,
|
|
8
|
+
LATEST_PROTOCOL_VERSION,
|
|
9
|
+
type StdioTransport,
|
|
10
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
11
|
+
} from "./stdio.ts";
|
|
12
|
+
|
|
13
|
+
const JSONRPC_ERROR = {
|
|
14
|
+
parse: -32700,
|
|
15
|
+
invalidRequest: -32600,
|
|
16
|
+
methodNotFound: -32601,
|
|
17
|
+
invalidParams: -32602,
|
|
18
|
+
internal: -32603,
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
type ToolHandler<Args> = (args: Args) => Promise<{
|
|
22
|
+
content: Array<{ type: "text"; text: string }>;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
type RegisteredTool = {
|
|
26
|
+
description: string;
|
|
27
|
+
inputSchema: Record<string, z.ZodType>;
|
|
28
|
+
handler: ToolHandler<Record<string, unknown>>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type MiruMcpServerInfo = {
|
|
32
|
+
name: string;
|
|
33
|
+
version: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class MiruMcpServer {
|
|
37
|
+
readonly serverInfo: MiruMcpServerInfo;
|
|
38
|
+
private readonly instructions?: string;
|
|
39
|
+
private readonly tools = new Map<string, RegisteredTool>();
|
|
40
|
+
private transport: StdioTransport | null = null;
|
|
41
|
+
|
|
42
|
+
constructor(serverInfo: MiruMcpServerInfo, options?: { instructions?: string }) {
|
|
43
|
+
this.serverInfo = serverInfo;
|
|
44
|
+
this.instructions = options?.instructions;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
registerTool<S extends Record<string, z.ZodType>>(
|
|
48
|
+
name: string,
|
|
49
|
+
config: {
|
|
50
|
+
description: string;
|
|
51
|
+
inputSchema: S;
|
|
52
|
+
},
|
|
53
|
+
handler: ToolHandler<z.infer<z.ZodObject<S>>>,
|
|
54
|
+
): void {
|
|
55
|
+
this.tools.set(name, {
|
|
56
|
+
description: config.description,
|
|
57
|
+
inputSchema: config.inputSchema,
|
|
58
|
+
handler: handler as ToolHandler<Record<string, unknown>>,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async connect(transport: StdioTransport): Promise<void> {
|
|
63
|
+
this.transport = transport;
|
|
64
|
+
transport.onmessage = async (message) => {
|
|
65
|
+
await this.handleMessage(message);
|
|
66
|
+
};
|
|
67
|
+
transport.onerror = (error) => {
|
|
68
|
+
console.error(error);
|
|
69
|
+
};
|
|
70
|
+
await transport.start();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async close(): Promise<void> {
|
|
74
|
+
await this.transport?.close();
|
|
75
|
+
this.transport = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async handleMessage(message: JsonRpcMessage): Promise<void> {
|
|
79
|
+
if (!isRequest(message)) {
|
|
80
|
+
if (isNotification(message)) {
|
|
81
|
+
if (message.method === "notifications/initialized") {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = await this.dispatchRequest(message);
|
|
90
|
+
if (result !== undefined) {
|
|
91
|
+
await this.send({
|
|
92
|
+
jsonrpc: "2.0",
|
|
93
|
+
id: message.id,
|
|
94
|
+
result,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
await this.sendError(message.id, error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async dispatchRequest(request: JsonRpcRequest): Promise<unknown> {
|
|
103
|
+
switch (request.method) {
|
|
104
|
+
case "initialize":
|
|
105
|
+
return this.handleInitialize(request.params);
|
|
106
|
+
case "ping":
|
|
107
|
+
return {};
|
|
108
|
+
case "tools/list":
|
|
109
|
+
return { tools: this.listTools() };
|
|
110
|
+
case "tools/call":
|
|
111
|
+
return await this.handleToolCall(request.params);
|
|
112
|
+
default:
|
|
113
|
+
throw rpcError(JSONRPC_ERROR.methodNotFound, `Method not found: ${request.method}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private handleInitialize(params: unknown): Record<string, unknown> {
|
|
118
|
+
const input = z
|
|
119
|
+
.object({
|
|
120
|
+
protocolVersion: z.string(),
|
|
121
|
+
capabilities: z.record(z.string(), z.unknown()).optional(),
|
|
122
|
+
clientInfo: z
|
|
123
|
+
.object({
|
|
124
|
+
name: z.string(),
|
|
125
|
+
version: z.string(),
|
|
126
|
+
})
|
|
127
|
+
.optional(),
|
|
128
|
+
})
|
|
129
|
+
.safeParse(params);
|
|
130
|
+
|
|
131
|
+
if (!input.success) {
|
|
132
|
+
throw rpcError(JSONRPC_ERROR.invalidParams, "Invalid initialize params");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const requestedVersion = input.data.protocolVersion;
|
|
136
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(
|
|
137
|
+
requestedVersion as (typeof SUPPORTED_PROTOCOL_VERSIONS)[number],
|
|
138
|
+
)
|
|
139
|
+
? requestedVersion
|
|
140
|
+
: LATEST_PROTOCOL_VERSION;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
protocolVersion,
|
|
144
|
+
capabilities: this.getCapabilities(),
|
|
145
|
+
serverInfo: this.serverInfo,
|
|
146
|
+
...(this.instructions ? { instructions: this.instructions } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private getCapabilities(): Record<string, unknown> {
|
|
151
|
+
if (this.tools.size === 0) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
tools: {
|
|
156
|
+
listChanged: true,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private listTools(): Array<Record<string, unknown>> {
|
|
162
|
+
return [...this.tools.entries()].map(([name, tool]) => ({
|
|
163
|
+
name,
|
|
164
|
+
description: tool.description,
|
|
165
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async handleToolCall(params: unknown): Promise<Record<string, unknown>> {
|
|
170
|
+
const input = z
|
|
171
|
+
.object({
|
|
172
|
+
name: z.string(),
|
|
173
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
174
|
+
})
|
|
175
|
+
.safeParse(params);
|
|
176
|
+
|
|
177
|
+
if (!input.success) {
|
|
178
|
+
throw rpcError(JSONRPC_ERROR.invalidParams, "Invalid tools/call params");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const tool = this.tools.get(input.data.name);
|
|
182
|
+
if (!tool) {
|
|
183
|
+
throw rpcError(JSONRPC_ERROR.invalidParams, `Tool ${input.data.name} not found`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const args = input.data.arguments ?? {};
|
|
187
|
+
const parsed = z.object(tool.inputSchema).safeParse(args);
|
|
188
|
+
if (!parsed.success) {
|
|
189
|
+
throw rpcError(JSONRPC_ERROR.invalidParams, parsed.error.message);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return await tool.handler(parsed.data);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async send(message: JsonRpcResponse): Promise<void> {
|
|
196
|
+
await this.transport?.send(message);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async sendError(id: JsonRpcId, error: unknown): Promise<void> {
|
|
200
|
+
const rpc = error instanceof RpcError ? error : rpcError(JSONRPC_ERROR.internal, String(error));
|
|
201
|
+
const message: JsonRpcError = {
|
|
202
|
+
jsonrpc: "2.0",
|
|
203
|
+
id,
|
|
204
|
+
error: {
|
|
205
|
+
code: rpc.code,
|
|
206
|
+
message: rpc.message,
|
|
207
|
+
...(rpc.data !== undefined ? { data: rpc.data } : {}),
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
await this.transport?.send(message);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class RpcError extends Error {
|
|
215
|
+
constructor(
|
|
216
|
+
readonly code: number,
|
|
217
|
+
message: string,
|
|
218
|
+
readonly data?: unknown,
|
|
219
|
+
) {
|
|
220
|
+
super(message);
|
|
221
|
+
this.name = "RpcError";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function rpcError(code: number, message: string, data?: unknown): RpcError {
|
|
226
|
+
return new RpcError(code, message, data);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isRequest(message: JsonRpcMessage): message is JsonRpcRequest {
|
|
230
|
+
return "method" in message && "id" in message;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isNotification(message: JsonRpcMessage): message is { jsonrpc: "2.0"; method: string } {
|
|
234
|
+
return "method" in message && !("id" in message);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function shapeToJsonSchema(shape: Record<string, z.ZodType>): Record<string, unknown> {
|
|
238
|
+
const schema = z.toJSONSchema(z.object(shape)) as Record<string, unknown>;
|
|
239
|
+
const { $schema: _schema, ...rest } = schema;
|
|
240
|
+
return rest;
|
|
241
|
+
}
|
package/src/mcp/serve.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
1
|
import type { ContentType } from "../types.ts";
|
|
3
2
|
import { IndexCache } from "./index-cache.ts";
|
|
4
3
|
import { createMcpServer } from "./server.ts";
|
|
4
|
+
import { StdioTransport } from "./stdio.ts";
|
|
5
5
|
|
|
6
6
|
export async function serveMcp(options: {
|
|
7
7
|
ref?: string | null;
|
|
@@ -9,6 +9,6 @@ export async function serveMcp(options: {
|
|
|
9
9
|
}): Promise<void> {
|
|
10
10
|
const cache = new IndexCache(options.content ?? ["code"], options.ref ?? null);
|
|
11
11
|
const server = createMcpServer(cache);
|
|
12
|
-
const transport = new
|
|
12
|
+
const transport = new StdioTransport();
|
|
13
13
|
await server.connect(transport);
|
|
14
14
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
1
|
import * as z from "zod";
|
|
3
2
|
import packageJson from "../../package.json";
|
|
4
3
|
import {
|
|
@@ -20,14 +19,15 @@ import {
|
|
|
20
19
|
resolveChunk,
|
|
21
20
|
} from "../utils.ts";
|
|
22
21
|
import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
|
|
22
|
+
import { MiruMcpServer } from "./runtime.ts";
|
|
23
23
|
|
|
24
24
|
const REPO_DESCRIPTION =
|
|
25
25
|
"https:// or http:// git URL (e.g. https://github.com/org/repo) or local directory path to index and search. " +
|
|
26
26
|
"Pass the project root for local workspaces. " +
|
|
27
27
|
"The index is built on the first tool call and cached for the session.";
|
|
28
28
|
|
|
29
|
-
export function createMcpServer(cache: IndexCache):
|
|
30
|
-
const server = new
|
|
29
|
+
export function createMcpServer(cache: IndexCache): MiruMcpServer {
|
|
30
|
+
const server = new MiruMcpServer(
|
|
31
31
|
{
|
|
32
32
|
name: "miru",
|
|
33
33
|
version: packageJson.version,
|
package/src/mcp/stdio.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export type JsonRpcId = string | number;
|
|
2
|
+
|
|
3
|
+
export type JsonRpcRequest = {
|
|
4
|
+
jsonrpc: "2.0";
|
|
5
|
+
id: JsonRpcId;
|
|
6
|
+
method: string;
|
|
7
|
+
params?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type JsonRpcNotification = {
|
|
11
|
+
jsonrpc: "2.0";
|
|
12
|
+
method: string;
|
|
13
|
+
params?: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type JsonRpcResponse = {
|
|
17
|
+
jsonrpc: "2.0";
|
|
18
|
+
id: JsonRpcId;
|
|
19
|
+
result?: unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type JsonRpcError = {
|
|
23
|
+
jsonrpc: "2.0";
|
|
24
|
+
id: JsonRpcId;
|
|
25
|
+
error: {
|
|
26
|
+
code: number;
|
|
27
|
+
message: string;
|
|
28
|
+
data?: unknown;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse | JsonRpcError;
|
|
33
|
+
|
|
34
|
+
export const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
35
|
+
"2025-11-25",
|
|
36
|
+
"2025-06-18",
|
|
37
|
+
"2025-03-26",
|
|
38
|
+
"2024-11-05",
|
|
39
|
+
"2024-10-07",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export const LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
43
|
+
|
|
44
|
+
export class StdioTransport {
|
|
45
|
+
private buffer = "";
|
|
46
|
+
private started = false;
|
|
47
|
+
private abortController: AbortController | null = null;
|
|
48
|
+
|
|
49
|
+
onmessage?: (message: JsonRpcMessage) => void | Promise<void>;
|
|
50
|
+
onerror?: (error: Error) => void;
|
|
51
|
+
onclose?: () => void;
|
|
52
|
+
|
|
53
|
+
async start(): Promise<void> {
|
|
54
|
+
if (this.started) {
|
|
55
|
+
throw new Error("StdioTransport already started");
|
|
56
|
+
}
|
|
57
|
+
this.started = true;
|
|
58
|
+
this.abortController = new AbortController();
|
|
59
|
+
const reader = Bun.stdin.stream().getReader();
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
while (!this.abortController.signal.aborted) {
|
|
64
|
+
const { done, value } = await reader.read();
|
|
65
|
+
if (done) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
await this.append(decoder.decode(value, { stream: true }));
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!this.abortController.signal.aborted) {
|
|
72
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
reader.releaseLock();
|
|
76
|
+
this.onclose?.();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async close(): Promise<void> {
|
|
81
|
+
this.abortController?.abort();
|
|
82
|
+
this.buffer = "";
|
|
83
|
+
this.onclose?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async send(message: JsonRpcMessage): Promise<void> {
|
|
87
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
88
|
+
await Bun.write(Bun.stdout, line);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async append(chunk: string): Promise<void> {
|
|
92
|
+
this.buffer += chunk;
|
|
93
|
+
while (true) {
|
|
94
|
+
const newline = this.buffer.indexOf("\n");
|
|
95
|
+
if (newline === -1) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const line = this.buffer.slice(0, newline).replace(/\r$/, "");
|
|
99
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
100
|
+
if (line.length === 0) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const message = JSON.parse(line) as JsonRpcMessage;
|
|
105
|
+
await Promise.resolve(this.onmessage?.(message));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|