xcode-copilot-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/config.json5 +49 -0
- package/dist/config-schema.d.ts +85 -0
- package/dist/config-schema.js +38 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +8 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/copilot-service.d.ts +19 -0
- package/dist/copilot-service.js +49 -0
- package/dist/copilot-service.js.map +1 -0
- package/dist/handlers/completions/session-config.d.ts +12 -0
- package/dist/handlers/completions/session-config.js +63 -0
- package/dist/handlers/completions/session-config.js.map +1 -0
- package/dist/handlers/completions/streaming.d.ts +4 -0
- package/dist/handlers/completions/streaming.js +132 -0
- package/dist/handlers/completions/streaming.js.map +1 -0
- package/dist/handlers/completions.d.ts +4 -0
- package/dist/handlers/completions.js +106 -0
- package/dist/handlers/completions.js.map +1 -0
- package/dist/handlers/models.d.ts +4 -0
- package/dist/handlers/models.js +29 -0
- package/dist/handlers/models.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +19 -0
- package/dist/logger.js +43 -0
- package/dist/logger.js.map +1 -0
- package/dist/schemas.d.ts +49 -0
- package/dist/schemas.js +75 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +41 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/prompt.d.ts +10 -0
- package/dist/utils/prompt.js +48 -0
- package/dist/utils/prompt.js.map +1 -0
- package/package.json +48 -0
- package/scripts/mcpbridge-proxy.mjs +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAiC,MAAM,SAAS,CAAC;AACxD,OAAO,IAAI,MAAM,eAAe,CAAC;AAGjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,MAAM,UAAU,GAA6B;IAC3C,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,MAAM;IACf,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC;QAClB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS;QAC/B,MAAM,EAAE;YACN,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;SACpC;KACF,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;QACvB,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC;QACnC,cAAc,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;KAClD,CAAC,CAAC;IAEH,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,gDAAgD,EAAE,EAAE,CACrD,CAAC;YACF,KAAK,KAAK;iBACP,IAAI,CAAC,GAAG,CAAC;iBACT,IAAI,CAAC,kBAAkB,CAAC;iBACxB,IAAI,CAAC,yBAAyB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,GAAG,CAAC,IAAI,CAAC,sBAAsB,EAAE,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;IAEhE,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface ContentPart {
|
|
2
|
+
type: string;
|
|
3
|
+
text?: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
export type MessageContent = string | ContentPart[] | null;
|
|
6
|
+
export interface ToolCallFunction {
|
|
7
|
+
name: string;
|
|
8
|
+
arguments: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ToolCall {
|
|
11
|
+
index?: number | undefined;
|
|
12
|
+
id?: string | undefined;
|
|
13
|
+
type?: string | undefined;
|
|
14
|
+
function: ToolCallFunction;
|
|
15
|
+
}
|
|
16
|
+
export interface Message {
|
|
17
|
+
role?: "system" | "developer" | "user" | "assistant" | "tool" | undefined;
|
|
18
|
+
content?: MessageContent | undefined;
|
|
19
|
+
name?: string | undefined;
|
|
20
|
+
tool_calls?: ToolCall[] | undefined;
|
|
21
|
+
tool_call_id?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface ToolFunction {
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string | undefined;
|
|
26
|
+
parameters?: Record<string, unknown> | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface Tool {
|
|
29
|
+
type: string;
|
|
30
|
+
function: ToolFunction;
|
|
31
|
+
}
|
|
32
|
+
export interface ChatCompletionRequest {
|
|
33
|
+
model: string;
|
|
34
|
+
messages: Message[];
|
|
35
|
+
temperature?: number | undefined;
|
|
36
|
+
top_p?: number | undefined;
|
|
37
|
+
n?: number | undefined;
|
|
38
|
+
stop?: string | string[] | undefined;
|
|
39
|
+
max_tokens?: number | undefined;
|
|
40
|
+
presence_penalty?: number | undefined;
|
|
41
|
+
frequency_penalty?: number | undefined;
|
|
42
|
+
tools?: Tool[] | undefined;
|
|
43
|
+
tool_choice?: unknown;
|
|
44
|
+
user?: string | undefined;
|
|
45
|
+
}
|
|
46
|
+
export interface Choice {
|
|
47
|
+
index: number;
|
|
48
|
+
message?: Message | undefined;
|
|
49
|
+
delta?: Partial<Message> | undefined;
|
|
50
|
+
finish_reason: string | null;
|
|
51
|
+
}
|
|
52
|
+
export interface Usage {
|
|
53
|
+
prompt_tokens: number;
|
|
54
|
+
completion_tokens: number;
|
|
55
|
+
total_tokens: number;
|
|
56
|
+
}
|
|
57
|
+
export interface ChatCompletionChunk {
|
|
58
|
+
id: string;
|
|
59
|
+
object: "chat.completion.chunk";
|
|
60
|
+
created: number;
|
|
61
|
+
model: string;
|
|
62
|
+
choices: Choice[];
|
|
63
|
+
system_fingerprint?: string | undefined;
|
|
64
|
+
}
|
|
65
|
+
export interface Model {
|
|
66
|
+
id: string;
|
|
67
|
+
object: "model";
|
|
68
|
+
created: number;
|
|
69
|
+
owned_by: string;
|
|
70
|
+
}
|
|
71
|
+
export interface ModelsResponse {
|
|
72
|
+
object: "list";
|
|
73
|
+
data: Model[];
|
|
74
|
+
}
|
|
75
|
+
export interface ErrorDetail {
|
|
76
|
+
message: string;
|
|
77
|
+
type: "invalid_request_error" | "api_error";
|
|
78
|
+
param?: string | null | undefined;
|
|
79
|
+
code?: string | null | undefined;
|
|
80
|
+
}
|
|
81
|
+
export interface ErrorResponse {
|
|
82
|
+
error: ErrorDetail;
|
|
83
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Strips fenced code blocks whose filename matches any pattern.
|
|
4
|
+
*
|
|
5
|
+
* Xcode search dumps full file contents for every match — excluded files
|
|
6
|
+
* can be thousands of lines and add nothing useful to the prompt.
|
|
7
|
+
*/
|
|
8
|
+
export declare function filterExcludedFiles(s: string, patterns: string[]): string;
|
|
9
|
+
/** System/developer messages are skipped — they're passed via `SessionConfig.systemMessage`. */
|
|
10
|
+
export declare function formatPrompt(messages: Message[], excludedFilePatterns: string[]): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { extractContentText } from "../schemas.js";
|
|
2
|
+
function escapeRegex(s) {
|
|
3
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Strips fenced code blocks whose filename matches any pattern.
|
|
7
|
+
*
|
|
8
|
+
* Xcode search dumps full file contents for every match — excluded files
|
|
9
|
+
* can be thousands of lines and add nothing useful to the prompt.
|
|
10
|
+
*/
|
|
11
|
+
export function filterExcludedFiles(s, patterns) {
|
|
12
|
+
if (patterns.length === 0)
|
|
13
|
+
return s;
|
|
14
|
+
const joined = patterns.map(escapeRegex).join("|");
|
|
15
|
+
const re = new RegExp("```\\w*:[^\\n]*(?:" + joined + ")[^\\n]*\\n.*?\\n```\\n?", "gis");
|
|
16
|
+
return s.replace(re, "");
|
|
17
|
+
}
|
|
18
|
+
/** System/developer messages are skipped — they're passed via `SessionConfig.systemMessage`. */
|
|
19
|
+
export function formatPrompt(messages, excludedFilePatterns) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
for (const msg of messages) {
|
|
22
|
+
const content = extractContentText(msg.content);
|
|
23
|
+
switch (msg.role) {
|
|
24
|
+
case "system":
|
|
25
|
+
case "developer":
|
|
26
|
+
// Handled via SessionConfig.systemMessage
|
|
27
|
+
continue;
|
|
28
|
+
case "user":
|
|
29
|
+
parts.push(`[User]: ${filterExcludedFiles(content, excludedFilePatterns)}`);
|
|
30
|
+
break;
|
|
31
|
+
case "assistant":
|
|
32
|
+
if (content) {
|
|
33
|
+
parts.push(`[Assistant]: ${content}`);
|
|
34
|
+
}
|
|
35
|
+
if (msg.tool_calls) {
|
|
36
|
+
for (const tc of msg.tool_calls) {
|
|
37
|
+
parts.push(`[Assistant called tool ${tc.function.name} with args: ${tc.function.arguments}]`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case "tool":
|
|
42
|
+
parts.push(`[Tool result for ${msg.tool_call_id ?? "unknown"}]: ${content}`);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return parts.join("\n\n");
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/utils/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAS,EAAE,QAAkB;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEpC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,IAAI,MAAM,CACnB,oBAAoB,GAAG,MAAM,GAAG,0BAA0B,EAC1D,KAAK,CACN,CAAC;IACF,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,YAAY,CAC1B,QAAmB,EACnB,oBAA8B;IAE9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEhD,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,QAAQ,CAAC;YACd,KAAK,WAAW;gBACd,0CAA0C;gBAC1C,SAAS;YAEX,KAAK,MAAM;gBACT,KAAK,CAAC,IAAI,CAAC,WAAW,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAC,CAAC;gBAC5E,MAAM;YAER,KAAK,WAAW;gBACd,IAAI,OAAO,EAAE,CAAC;oBACZ,KAAK,CAAC,IAAI,CAAC,gBAAgB,OAAO,EAAE,CAAC,CAAC;gBACxC,CAAC;gBACD,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;oBACnB,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;wBAChC,KAAK,CAAC,IAAI,CACR,0BAA0B,EAAE,CAAC,QAAQ,CAAC,IAAI,eAAe,EAAE,CAAC,QAAQ,CAAC,SAAS,GAAG,CAClF,CAAC;oBACJ,CAAC;gBACH,CAAC;gBACD,MAAM;YAER,KAAK,MAAM;gBACT,KAAK,CAAC,IAAI,CAAC,oBAAoB,GAAG,CAAC,YAAY,IAAI,SAAS,MAAM,OAAO,EAAE,CAAC,CAAC;gBAC7E,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xcode-copilot-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenAI-compatible proxy API server for Xcode, powered by GitHub Copilot",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/theblixguy/xcode-copilot-server.git"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": "25.6.0"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"xcode-copilot-server": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"config.json5",
|
|
20
|
+
"scripts/"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"dev": "tsx src/index.ts",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@fastify/cors": "11.2.0",
|
|
34
|
+
"@github/copilot-sdk": "0.1.23",
|
|
35
|
+
"fastify": "5.7.4",
|
|
36
|
+
"json5": "2.2.3",
|
|
37
|
+
"zod": "4.3.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@eslint/js": "9.39.2",
|
|
41
|
+
"@types/node": "25.2.1",
|
|
42
|
+
"eslint": "9.39.2",
|
|
43
|
+
"tsx": "4.21.0",
|
|
44
|
+
"typescript": "5.9.3",
|
|
45
|
+
"typescript-eslint": "8.54.0",
|
|
46
|
+
"vitest": "4.0.18"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP stdio proxy for Apple's `xcrun mcpbridge`.
|
|
5
|
+
*
|
|
6
|
+
* mcpbridge declares output schemas on its tools but returns
|
|
7
|
+
* results via the `content` field (text array) without the corresponding
|
|
8
|
+
* `structuredContent` field that the MCP spec requires. The Copilot CLI
|
|
9
|
+
* enforces this strictly and rejects the response with error -32600.
|
|
10
|
+
*
|
|
11
|
+
* So, intercept every JSON-RPC response from mcpbridge. When a response
|
|
12
|
+
* carries `content` but no `structuredContent`, synthesise it from the
|
|
13
|
+
* first text content item — parsing as JSON when possible, falling back
|
|
14
|
+
* to a `{ text }` wrapper otherwise.
|
|
15
|
+
*
|
|
16
|
+
* Source: https://rudrank.com/exploring-xcode-using-mcp-tools-cursor-external-clients
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawn, execFile } from "node:child_process";
|
|
20
|
+
import { createInterface } from "node:readline";
|
|
21
|
+
import { promisify } from "node:util";
|
|
22
|
+
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
24
|
+
|
|
25
|
+
function patchIfNeeded(msg) {
|
|
26
|
+
const result = msg.result;
|
|
27
|
+
if (!result || typeof result !== "object") return;
|
|
28
|
+
if (!Array.isArray(result.content) || result.content.length === 0) return;
|
|
29
|
+
if (result.structuredContent !== undefined) return;
|
|
30
|
+
|
|
31
|
+
const textItem = result.content.find((c) => c.type === "text");
|
|
32
|
+
if (!textItem?.text) return;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
result.structuredContent = JSON.parse(textItem.text);
|
|
36
|
+
} catch {
|
|
37
|
+
result.structuredContent = { text: textItem.text };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await execFileAsync("xcrun", ["--find", "mcpbridge"]);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(
|
|
45
|
+
"Error: xcrun mcpbridge not found. This requires Xcode 26.3 or later."
|
|
46
|
+
);
|
|
47
|
+
console.error(
|
|
48
|
+
"Please install Xcode 26.3+ or remove the 'xcode' MCP server from config.json5"
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bridge = spawn("xcrun", ["mcpbridge", ...process.argv.slice(2)], {
|
|
54
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
process.stdin.pipe(bridge.stdin);
|
|
58
|
+
|
|
59
|
+
const reader = createInterface({ input: bridge.stdout, crlfDelay: Infinity });
|
|
60
|
+
|
|
61
|
+
reader.on("line", (line) => {
|
|
62
|
+
try {
|
|
63
|
+
const msg = JSON.parse(line);
|
|
64
|
+
patchIfNeeded(msg);
|
|
65
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
66
|
+
} catch {
|
|
67
|
+
process.stdout.write(line + "\n");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
bridge.on("exit", (code) => process.exit(code ?? 0));
|
|
72
|
+
process.on("SIGTERM", () => bridge.kill("SIGTERM"));
|
|
73
|
+
process.on("SIGINT", () => bridge.kill("SIGINT"));
|