@braid-cloud/mcp 0.1.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/README.md +176 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +353 -0
- package/dist/cli.js.map +1 -0
- package/dist/proxy.d.ts +38 -0
- package/dist/proxy.js +354 -0
- package/dist/proxy.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# @braid-cloud/mcp
|
|
2
|
+
|
|
3
|
+
braid MCP stdio proxy - enables tools that don't support remote MCP (like Zed) to use braid.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @braid-cloud/mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. Get a Personal Access Token from [braid](https://app.braid.cloud)
|
|
14
|
+
2. Set the token as an environment variable:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
export BRAID_TOKEN=br_your_token_here
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Command Line
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
braid-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The proxy runs in stdio mode, accepting MCP requests on stdin and responding on stdout.
|
|
29
|
+
|
|
30
|
+
### Zed Configuration
|
|
31
|
+
|
|
32
|
+
Add to your Zed settings:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"context_servers": {
|
|
37
|
+
"braid": {
|
|
38
|
+
"command": {
|
|
39
|
+
"path": "braid-mcp",
|
|
40
|
+
"args": [],
|
|
41
|
+
"env": {
|
|
42
|
+
"BRAID_TOKEN": "br_your_token_here"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Claude Desktop Configuration
|
|
51
|
+
|
|
52
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"braid": {
|
|
58
|
+
"command": "braid-mcp",
|
|
59
|
+
"env": {
|
|
60
|
+
"BRAID_TOKEN": "br_your_token_here"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Environment Variables
|
|
68
|
+
|
|
69
|
+
| Variable | Required | Description |
|
|
70
|
+
| --------------------- | -------- | ------------------------------------------------------------------------ |
|
|
71
|
+
| `BRAID_TOKEN` | Yes\* | Your braid Personal Access Token |
|
|
72
|
+
| `BRAID_MCP_URL` | No | Custom MCP server URL (defaults to `https://app.braid.cloud/api/mcp`) |
|
|
73
|
+
|
|
74
|
+
\* Token can also be provided via `braid.user.json` (see below)
|
|
75
|
+
|
|
76
|
+
## Repository Configuration
|
|
77
|
+
|
|
78
|
+
The proxy automatically discovers and loads configuration from your repository, enabling project-specific settings.
|
|
79
|
+
|
|
80
|
+
### Config Files
|
|
81
|
+
|
|
82
|
+
- **`braid.json`** - Committable team config (projects, default profile)
|
|
83
|
+
- **`braid.user.json`** - Gitignored personal overrides (token, personal projects)
|
|
84
|
+
|
|
85
|
+
The proxy searches upward from the current directory to find these files.
|
|
86
|
+
|
|
87
|
+
### Example: braid.json (commit to repo)
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"$schema": "https://braid.cloud/schemas/config.json",
|
|
92
|
+
"context": {
|
|
93
|
+
"orgId": "org_abc123",
|
|
94
|
+
"orgProjectIds": ["proj_frontend", "proj_shared"],
|
|
95
|
+
"profileName": "web-development"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Example: braid.user.json (add to .gitignore)
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"token": "br_your_personal_token",
|
|
105
|
+
"context": {
|
|
106
|
+
"personalProjectIds": ["proj_my_experiments"],
|
|
107
|
+
"profileName": "my-custom-profile"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Context Options
|
|
113
|
+
|
|
114
|
+
| Field | Type | Description |
|
|
115
|
+
| -------------------- | -------- | ------------------------------------------------------- |
|
|
116
|
+
| `profileName` | string | Named profile to use (stored in braid) |
|
|
117
|
+
| `orgId` | string | Organization ID to scope searches |
|
|
118
|
+
| `orgProjectIds` | string[] | Org project IDs to include in search |
|
|
119
|
+
| `personalProjectIds` | string[] | Personal project IDs to include |
|
|
120
|
+
| `includeUserGlobal` | boolean | Include user's global rules (default: true) |
|
|
121
|
+
| `includeOrgGlobal` | boolean | Include org's global rules (default: true if orgId set) |
|
|
122
|
+
| `resolveOverlays` | boolean | Auto-resolve personal overlays (default: true) |
|
|
123
|
+
|
|
124
|
+
### Config Merging
|
|
125
|
+
|
|
126
|
+
User config overrides base config:
|
|
127
|
+
|
|
128
|
+
1. Fields in `braid.user.json` take precedence
|
|
129
|
+
2. Token from user config is preferred over env variable
|
|
130
|
+
3. Context fields are merged (user overrides base)
|
|
131
|
+
|
|
132
|
+
## Available Tools
|
|
133
|
+
|
|
134
|
+
The proxy forwards all tools from the braid MCP server:
|
|
135
|
+
|
|
136
|
+
- **rules.search** - Search for rules using natural language queries
|
|
137
|
+
- **context.get** - Get current context configuration
|
|
138
|
+
- **projects.list** - List available projects
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Your Tool (Zed, Claude Desktop, etc.)
|
|
144
|
+
│ stdio (stdin/stdout)
|
|
145
|
+
▼
|
|
146
|
+
braid-mcp (this package)
|
|
147
|
+
│ HTTP + Bearer token
|
|
148
|
+
▼
|
|
149
|
+
braid MCP Server
|
|
150
|
+
│
|
|
151
|
+
▼
|
|
152
|
+
braid Backend
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The proxy creates a stdio-based MCP server that forwards all requests to the hosted braid MCP server, handling authentication automatically.
|
|
156
|
+
|
|
157
|
+
## Programmatic Usage
|
|
158
|
+
|
|
159
|
+
You can also use this package programmatically:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createProxyServer } from "@braid-cloud/mcp";
|
|
163
|
+
|
|
164
|
+
const { server, client, close } = await createProxyServer({
|
|
165
|
+
token: "br_your_token_here",
|
|
166
|
+
serverUrl: "https://app.braid.cloud/api/mcp", // optional
|
|
167
|
+
repoContext: { profileName: "my-profile" }, // optional
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Later, when done:
|
|
171
|
+
await close();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/proxy.ts
|
|
4
|
+
import process2 from "process";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema
|
|
12
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { Data as Data2, Effect as Effect2, pipe as pipe2 } from "effect";
|
|
14
|
+
|
|
15
|
+
// src/config.ts
|
|
16
|
+
import { readFile } from "fs/promises";
|
|
17
|
+
import { dirname, join, resolve } from "path";
|
|
18
|
+
import process from "process";
|
|
19
|
+
import { Data, Effect, pipe } from "effect";
|
|
20
|
+
var CONFIG_FILENAME = "braid.json";
|
|
21
|
+
var USER_CONFIG_FILENAME = "braid.user.json";
|
|
22
|
+
var FileReadError = class extends Data.TaggedError("FileReadError") {
|
|
23
|
+
};
|
|
24
|
+
var readJsonFile = (path) => pipe(
|
|
25
|
+
Effect.tryPromise({
|
|
26
|
+
try: () => readFile(path, "utf-8"),
|
|
27
|
+
catch: () => new FileReadError({})
|
|
28
|
+
}),
|
|
29
|
+
Effect.flatMap(
|
|
30
|
+
(content) => pipe(
|
|
31
|
+
Effect.try(() => JSON.parse(content)),
|
|
32
|
+
Effect.orElseSucceed(() => null)
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
Effect.orElseSucceed(() => null)
|
|
36
|
+
);
|
|
37
|
+
var fileExists = (path) => pipe(
|
|
38
|
+
Effect.tryPromise({
|
|
39
|
+
try: () => readFile(path),
|
|
40
|
+
catch: () => new FileReadError({})
|
|
41
|
+
}),
|
|
42
|
+
Effect.map(() => true),
|
|
43
|
+
Effect.orElseSucceed(() => false)
|
|
44
|
+
);
|
|
45
|
+
var findConfigDir = (startDir) => {
|
|
46
|
+
const search = (currentDir) => {
|
|
47
|
+
const root = dirname(currentDir);
|
|
48
|
+
if (currentDir === root) {
|
|
49
|
+
return Effect.succeed(null);
|
|
50
|
+
}
|
|
51
|
+
const configPath = join(currentDir, CONFIG_FILENAME);
|
|
52
|
+
const userConfigPath = join(currentDir, USER_CONFIG_FILENAME);
|
|
53
|
+
return pipe(
|
|
54
|
+
Effect.all([fileExists(configPath), fileExists(userConfigPath)]),
|
|
55
|
+
Effect.flatMap(
|
|
56
|
+
([hasConfig, hasUserConfig]) => hasConfig || hasUserConfig ? Effect.succeed(currentDir) : search(dirname(currentDir))
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
return search(resolve(startDir));
|
|
61
|
+
};
|
|
62
|
+
var mergeConfigValues = (baseConfig, userConfig) => {
|
|
63
|
+
return {
|
|
64
|
+
orgProjects: userConfig?.orgProjects ?? baseConfig?.orgProjects,
|
|
65
|
+
personalProjects: userConfig?.personalProjects ?? baseConfig?.personalProjects,
|
|
66
|
+
profile: userConfig?.profile ?? baseConfig?.profile,
|
|
67
|
+
org: userConfig?.org ?? baseConfig?.org,
|
|
68
|
+
token: userConfig?.token ?? baseConfig?.token,
|
|
69
|
+
serverUrl: userConfig?.serverUrl ?? baseConfig?.serverUrl,
|
|
70
|
+
includeUserGlobalRules: userConfig?.includeUserGlobalRules ?? baseConfig?.includeUserGlobalRules,
|
|
71
|
+
includeOrgGlobalRules: userConfig?.includeOrgGlobalRules ?? baseConfig?.includeOrgGlobalRules,
|
|
72
|
+
mcpMode: userConfig?.mcpMode ?? baseConfig?.mcpMode
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
var filterDefinedValues = (merged, baseConfig, userConfig, configPath, userConfigPath) => {
|
|
76
|
+
const result = {};
|
|
77
|
+
if (merged.orgProjects !== void 0) {
|
|
78
|
+
result.orgProjects = merged.orgProjects;
|
|
79
|
+
}
|
|
80
|
+
if (merged.personalProjects !== void 0) {
|
|
81
|
+
result.personalProjects = merged.personalProjects;
|
|
82
|
+
}
|
|
83
|
+
if (merged.profile !== void 0) {
|
|
84
|
+
result.profile = merged.profile;
|
|
85
|
+
}
|
|
86
|
+
if (merged.org !== void 0) {
|
|
87
|
+
result.org = merged.org;
|
|
88
|
+
}
|
|
89
|
+
if (merged.token !== void 0) {
|
|
90
|
+
result.token = merged.token;
|
|
91
|
+
}
|
|
92
|
+
if (merged.serverUrl !== void 0) {
|
|
93
|
+
result.serverUrl = merged.serverUrl;
|
|
94
|
+
}
|
|
95
|
+
if (merged.includeUserGlobalRules !== void 0) {
|
|
96
|
+
result.includeUserGlobalRules = merged.includeUserGlobalRules;
|
|
97
|
+
}
|
|
98
|
+
if (merged.includeOrgGlobalRules !== void 0) {
|
|
99
|
+
result.includeOrgGlobalRules = merged.includeOrgGlobalRules;
|
|
100
|
+
}
|
|
101
|
+
if (merged.mcpMode !== void 0) {
|
|
102
|
+
result.mcpMode = merged.mcpMode;
|
|
103
|
+
}
|
|
104
|
+
if (baseConfig) {
|
|
105
|
+
result.configPath = configPath;
|
|
106
|
+
}
|
|
107
|
+
if (userConfig) {
|
|
108
|
+
result.userConfigPath = userConfigPath;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
};
|
|
112
|
+
var buildResolvedConfig = (baseConfig, userConfig, configPath, userConfigPath) => {
|
|
113
|
+
if (!(baseConfig || userConfig)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const merged = mergeConfigValues(baseConfig, userConfig);
|
|
117
|
+
return filterDefinedValues(
|
|
118
|
+
merged,
|
|
119
|
+
baseConfig,
|
|
120
|
+
userConfig,
|
|
121
|
+
configPath,
|
|
122
|
+
userConfigPath
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
var loadRepoConfig = (startDir) => {
|
|
126
|
+
const searchDir = startDir ?? process.env.BRAID_CONFIG_DIR ?? process.cwd();
|
|
127
|
+
return pipe(
|
|
128
|
+
findConfigDir(searchDir),
|
|
129
|
+
Effect.flatMap((configDir) => {
|
|
130
|
+
if (!configDir) {
|
|
131
|
+
return Effect.succeed(null);
|
|
132
|
+
}
|
|
133
|
+
const configPath = join(configDir, CONFIG_FILENAME);
|
|
134
|
+
const userConfigPath = join(configDir, USER_CONFIG_FILENAME);
|
|
135
|
+
return pipe(
|
|
136
|
+
Effect.all({
|
|
137
|
+
baseConfig: readJsonFile(configPath),
|
|
138
|
+
userConfig: readJsonFile(userConfigPath)
|
|
139
|
+
}),
|
|
140
|
+
Effect.map(
|
|
141
|
+
({ baseConfig, userConfig }) => buildResolvedConfig(
|
|
142
|
+
baseConfig,
|
|
143
|
+
userConfig,
|
|
144
|
+
configPath,
|
|
145
|
+
userConfigPath
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
var hasContextOverrides = (config) => Boolean(
|
|
153
|
+
config.orgProjects?.length || config.personalProjects?.length || config.profile || config.org
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// src/proxy.ts
|
|
157
|
+
var ProxyError = class extends Data2.TaggedError("ProxyError") {
|
|
158
|
+
};
|
|
159
|
+
var DEFAULT_SERVER_URL = "https://app.braid.cloud/api/mcp";
|
|
160
|
+
var createMcpClient = (serverUrl, token, mcpMode) => Effect2.tryPromise({
|
|
161
|
+
try: async () => {
|
|
162
|
+
const headers = {
|
|
163
|
+
Authorization: `Bearer ${token}`
|
|
164
|
+
};
|
|
165
|
+
if (mcpMode) {
|
|
166
|
+
headers["X-Braid-Mcp-Mode"] = mcpMode;
|
|
167
|
+
}
|
|
168
|
+
const clientTransport = new StreamableHTTPClientTransport(
|
|
169
|
+
new URL(serverUrl),
|
|
170
|
+
{
|
|
171
|
+
requestInit: {
|
|
172
|
+
headers
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
const client = new Client(
|
|
177
|
+
{ name: "braid-mcp-proxy", version: "0.1.0" },
|
|
178
|
+
{ capabilities: {} }
|
|
179
|
+
);
|
|
180
|
+
await client.connect(clientTransport);
|
|
181
|
+
return client;
|
|
182
|
+
},
|
|
183
|
+
catch: (e) => new ProxyError({
|
|
184
|
+
code: "connection_error",
|
|
185
|
+
message: e instanceof Error ? e.message : "Failed to connect to server"
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
var injectContext = (args, repoContext) => {
|
|
189
|
+
if (!(repoContext && hasContextOverrides(repoContext))) {
|
|
190
|
+
return args;
|
|
191
|
+
}
|
|
192
|
+
const result = { ...args };
|
|
193
|
+
if (repoContext.orgProjects !== void 0 && args.orgProjects === void 0) {
|
|
194
|
+
result.orgProjects = repoContext.orgProjects;
|
|
195
|
+
}
|
|
196
|
+
if (repoContext.personalProjects !== void 0 && args.personalProjects === void 0) {
|
|
197
|
+
result.personalProjects = repoContext.personalProjects;
|
|
198
|
+
}
|
|
199
|
+
if (repoContext.profile !== void 0 && args.profile === void 0) {
|
|
200
|
+
result.profile = repoContext.profile;
|
|
201
|
+
}
|
|
202
|
+
if (repoContext.org !== void 0 && args.org === void 0) {
|
|
203
|
+
result.org = repoContext.org;
|
|
204
|
+
}
|
|
205
|
+
if (repoContext.includeUserGlobalRules !== void 0 && args.includeUserGlobalRules === void 0) {
|
|
206
|
+
result.includeUserGlobalRules = repoContext.includeUserGlobalRules;
|
|
207
|
+
}
|
|
208
|
+
if (repoContext.includeOrgGlobalRules !== void 0 && args.includeOrgGlobalRules === void 0) {
|
|
209
|
+
result.includeOrgGlobalRules = repoContext.includeOrgGlobalRules;
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
};
|
|
213
|
+
var createMcpServer = (client, repoContext) => Effect2.tryPromise({
|
|
214
|
+
try: async () => {
|
|
215
|
+
const serverTransport = new StdioServerTransport();
|
|
216
|
+
const server = new Server(
|
|
217
|
+
{ name: "braid", version: "0.1.0" },
|
|
218
|
+
{ capabilities: { tools: {} } }
|
|
219
|
+
);
|
|
220
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
221
|
+
const result = await client.listTools();
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
225
|
+
const injectedArgs = injectContext(
|
|
226
|
+
request.params.arguments ?? {},
|
|
227
|
+
repoContext
|
|
228
|
+
);
|
|
229
|
+
const result = await client.callTool({
|
|
230
|
+
name: request.params.name,
|
|
231
|
+
arguments: injectedArgs
|
|
232
|
+
});
|
|
233
|
+
return result;
|
|
234
|
+
});
|
|
235
|
+
await server.connect(serverTransport);
|
|
236
|
+
return server;
|
|
237
|
+
},
|
|
238
|
+
catch: (e) => new ProxyError({
|
|
239
|
+
code: "runtime_error",
|
|
240
|
+
message: e instanceof Error ? e.message : "Failed to create server"
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
var createProxyServer = (config) => {
|
|
244
|
+
const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL;
|
|
245
|
+
const mcpMode = config.repoContext?.mcpMode;
|
|
246
|
+
return pipe2(
|
|
247
|
+
createMcpClient(serverUrl, config.token, mcpMode),
|
|
248
|
+
Effect2.flatMap(
|
|
249
|
+
(client) => pipe2(
|
|
250
|
+
createMcpServer(client, config.repoContext),
|
|
251
|
+
Effect2.map((server) => {
|
|
252
|
+
const close = async () => {
|
|
253
|
+
await server.close();
|
|
254
|
+
await client.close();
|
|
255
|
+
};
|
|
256
|
+
return { server, client, close };
|
|
257
|
+
})
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
var buildRepoContext = (repoConfig) => {
|
|
263
|
+
if (!repoConfig) {
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
const context = {};
|
|
267
|
+
if (repoConfig.orgProjects !== void 0) {
|
|
268
|
+
context.orgProjects = repoConfig.orgProjects;
|
|
269
|
+
}
|
|
270
|
+
if (repoConfig.personalProjects !== void 0) {
|
|
271
|
+
context.personalProjects = repoConfig.personalProjects;
|
|
272
|
+
}
|
|
273
|
+
if (repoConfig.profile !== void 0) {
|
|
274
|
+
context.profile = repoConfig.profile;
|
|
275
|
+
}
|
|
276
|
+
if (repoConfig.org !== void 0) {
|
|
277
|
+
context.org = repoConfig.org;
|
|
278
|
+
}
|
|
279
|
+
if (repoConfig.includeUserGlobalRules !== void 0) {
|
|
280
|
+
context.includeUserGlobalRules = repoConfig.includeUserGlobalRules;
|
|
281
|
+
}
|
|
282
|
+
if (repoConfig.includeOrgGlobalRules !== void 0) {
|
|
283
|
+
context.includeOrgGlobalRules = repoConfig.includeOrgGlobalRules;
|
|
284
|
+
}
|
|
285
|
+
if (repoConfig.mcpMode !== void 0) {
|
|
286
|
+
context.mcpMode = repoConfig.mcpMode;
|
|
287
|
+
}
|
|
288
|
+
return Object.keys(context).length > 0 ? context : void 0;
|
|
289
|
+
};
|
|
290
|
+
var logConfigPaths = (repoConfig) => {
|
|
291
|
+
if (repoConfig?.configPath) {
|
|
292
|
+
process2.stderr.write(`Loaded config from ${repoConfig.configPath}
|
|
293
|
+
`);
|
|
294
|
+
}
|
|
295
|
+
if (repoConfig?.userConfigPath) {
|
|
296
|
+
process2.stderr.write(
|
|
297
|
+
`Loaded user config from ${repoConfig.userConfigPath}
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var resolveConfig = () => pipe2(
|
|
303
|
+
loadRepoConfig(),
|
|
304
|
+
Effect2.flatMap((repoConfig) => {
|
|
305
|
+
const token = repoConfig?.token ?? process2.env.BRAID_TOKEN;
|
|
306
|
+
if (!token) {
|
|
307
|
+
return Effect2.fail(
|
|
308
|
+
new ProxyError({
|
|
309
|
+
code: "missing_token",
|
|
310
|
+
message: "BRAID_TOKEN environment variable is required"
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const serverUrl = repoConfig?.serverUrl ?? process2.env.BRAID_MCP_URL;
|
|
315
|
+
logConfigPaths(repoConfig);
|
|
316
|
+
return Effect2.succeed({
|
|
317
|
+
token,
|
|
318
|
+
serverUrl,
|
|
319
|
+
repoContext: buildRepoContext(repoConfig)
|
|
320
|
+
});
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
var startProxy = () => pipe2(
|
|
324
|
+
resolveConfig(),
|
|
325
|
+
Effect2.flatMap(createProxyServer),
|
|
326
|
+
Effect2.flatMap(
|
|
327
|
+
({ close }) => Effect2.async(() => {
|
|
328
|
+
const shutdown = async () => {
|
|
329
|
+
await close();
|
|
330
|
+
process2.exit(0);
|
|
331
|
+
};
|
|
332
|
+
process2.on("SIGINT", shutdown);
|
|
333
|
+
process2.on("SIGTERM", shutdown);
|
|
334
|
+
process2.stdin.resume();
|
|
335
|
+
})
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
var startProxyFromEnv = async () => {
|
|
339
|
+
await Effect2.runPromise(
|
|
340
|
+
pipe2(
|
|
341
|
+
startProxy(),
|
|
342
|
+
Effect2.catchAll((error) => {
|
|
343
|
+
process2.stderr.write(`Error starting proxy: ${error.message}
|
|
344
|
+
`);
|
|
345
|
+
return process2.exit(1);
|
|
346
|
+
})
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/cli.ts
|
|
352
|
+
startProxyFromEnv().catch(() => void 0);
|
|
353
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts","../src/config.ts","../src/cli.ts"],"sourcesContent":["import process from \"node:process\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Data, Effect, pipe } from \"effect\";\nimport type { RepoContextConfig, ResolvedRepoConfig } from \"./config.ts\";\nimport { hasContextOverrides, loadRepoConfig } from \"./config.ts\";\n\ninterface ProxyConfig {\n token: string;\n serverUrl?: string | undefined;\n repoContext?: RepoContextConfig | undefined;\n}\n\nclass ProxyError extends Data.TaggedError(\"ProxyError\")<{\n code: \"missing_token\" | \"connection_error\" | \"runtime_error\";\n message: string;\n}> {}\n\nconst DEFAULT_SERVER_URL = \"https://app.braid.cloud/api/mcp\";\n\nconst createMcpClient = (\n serverUrl: string,\n token: string,\n mcpMode?: string\n): Effect.Effect<Client, ProxyError> =>\n Effect.tryPromise({\n try: async () => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n };\n\n if (mcpMode) {\n headers[\"X-Braid-Mcp-Mode\"] = mcpMode;\n }\n\n const clientTransport = new StreamableHTTPClientTransport(\n new URL(serverUrl),\n {\n requestInit: {\n headers,\n },\n }\n );\n\n const client = new Client(\n { name: \"braid-mcp-proxy\", version: \"0.1.0\" },\n { capabilities: {} }\n );\n\n await client.connect(clientTransport as Transport);\n return client;\n },\n catch: (e) =>\n new ProxyError({\n code: \"connection_error\",\n message: e instanceof Error ? e.message : \"Failed to connect to server\",\n }),\n });\n\nconst injectContext = (\n args: Record<string, unknown>,\n repoContext: RepoContextConfig | undefined\n): Record<string, unknown> => {\n if (!(repoContext && hasContextOverrides(repoContext))) {\n return args;\n }\n\n const result = { ...args };\n\n if (repoContext.orgProjects !== undefined && args.orgProjects === undefined) {\n result.orgProjects = repoContext.orgProjects;\n }\n if (\n repoContext.personalProjects !== undefined &&\n args.personalProjects === undefined\n ) {\n result.personalProjects = repoContext.personalProjects;\n }\n if (repoContext.profile !== undefined && args.profile === undefined) {\n result.profile = repoContext.profile;\n }\n if (repoContext.org !== undefined && args.org === undefined) {\n result.org = repoContext.org;\n }\n if (\n repoContext.includeUserGlobalRules !== undefined &&\n args.includeUserGlobalRules === undefined\n ) {\n result.includeUserGlobalRules = repoContext.includeUserGlobalRules;\n }\n if (\n repoContext.includeOrgGlobalRules !== undefined &&\n args.includeOrgGlobalRules === undefined\n ) {\n result.includeOrgGlobalRules = repoContext.includeOrgGlobalRules;\n }\n\n return result;\n};\n\nconst createMcpServer = (\n client: Client,\n repoContext: RepoContextConfig | undefined\n): Effect.Effect<Server, ProxyError> =>\n Effect.tryPromise({\n try: async () => {\n const serverTransport = new StdioServerTransport();\n\n const server = new Server(\n { name: \"braid\", version: \"0.1.0\" },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const result = await client.listTools();\n return result;\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const injectedArgs = injectContext(\n request.params.arguments ?? {},\n repoContext\n );\n const result = await client.callTool({\n name: request.params.name,\n arguments: injectedArgs,\n });\n return result;\n });\n\n await server.connect(serverTransport);\n return server;\n },\n catch: (e) =>\n new ProxyError({\n code: \"runtime_error\",\n message: e instanceof Error ? e.message : \"Failed to create server\",\n }),\n });\n\nconst createProxyServer = (\n config: ProxyConfig\n): Effect.Effect<\n { server: Server; client: Client; close: () => Promise<void> },\n ProxyError\n> => {\n const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL;\n const mcpMode = config.repoContext?.mcpMode;\n\n return pipe(\n createMcpClient(serverUrl, config.token, mcpMode),\n Effect.flatMap((client) =>\n pipe(\n createMcpServer(client, config.repoContext),\n Effect.map((server) => {\n const close = async () => {\n await server.close();\n await client.close();\n };\n return { server, client, close };\n })\n )\n )\n );\n};\n\nconst buildRepoContext = (\n repoConfig: ResolvedRepoConfig | null\n): RepoContextConfig | undefined => {\n if (!repoConfig) {\n return undefined;\n }\n\n const context: RepoContextConfig = {};\n\n if (repoConfig.orgProjects !== undefined) {\n context.orgProjects = repoConfig.orgProjects;\n }\n if (repoConfig.personalProjects !== undefined) {\n context.personalProjects = repoConfig.personalProjects;\n }\n if (repoConfig.profile !== undefined) {\n context.profile = repoConfig.profile;\n }\n if (repoConfig.org !== undefined) {\n context.org = repoConfig.org;\n }\n if (repoConfig.includeUserGlobalRules !== undefined) {\n context.includeUserGlobalRules = repoConfig.includeUserGlobalRules;\n }\n if (repoConfig.includeOrgGlobalRules !== undefined) {\n context.includeOrgGlobalRules = repoConfig.includeOrgGlobalRules;\n }\n if (repoConfig.mcpMode !== undefined) {\n context.mcpMode = repoConfig.mcpMode;\n }\n\n return Object.keys(context).length > 0 ? context : undefined;\n};\n\nconst logConfigPaths = (\n repoConfig: {\n configPath?: string;\n userConfigPath?: string;\n mcpMode?: string;\n } | null\n): void => {\n if (repoConfig?.configPath) {\n process.stderr.write(`Loaded config from ${repoConfig.configPath}\\n`);\n }\n if (repoConfig?.userConfigPath) {\n process.stderr.write(\n `Loaded user config from ${repoConfig.userConfigPath}\\n`\n );\n }\n};\n\nconst resolveConfig = (): Effect.Effect<ProxyConfig, ProxyError> =>\n pipe(\n loadRepoConfig(),\n Effect.flatMap((repoConfig) => {\n const token = repoConfig?.token ?? process.env.BRAID_TOKEN;\n if (!token) {\n return Effect.fail(\n new ProxyError({\n code: \"missing_token\",\n message: \"BRAID_TOKEN environment variable is required\",\n })\n );\n }\n\n const serverUrl = repoConfig?.serverUrl ?? process.env.BRAID_MCP_URL;\n logConfigPaths(repoConfig);\n\n return Effect.succeed({\n token,\n serverUrl,\n repoContext: buildRepoContext(repoConfig),\n });\n })\n );\n\nconst startProxy = (): Effect.Effect<void, ProxyError> =>\n pipe(\n resolveConfig(),\n Effect.flatMap(createProxyServer),\n Effect.flatMap(({ close }) =>\n Effect.async<void, ProxyError>(() => {\n const shutdown = async () => {\n await close();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n process.stdin.resume();\n })\n )\n );\n\nconst startProxyFromEnv = async (): Promise<void> => {\n await Effect.runPromise(\n pipe(\n startProxy(),\n Effect.catchAll((error): Effect.Effect<never> => {\n process.stderr.write(`Error starting proxy: ${error.message}\\n`);\n return process.exit(1) as never;\n })\n )\n );\n};\n\nexport type { ProxyConfig };\nexport { createProxyServer, startProxyFromEnv };\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { Data, Effect, pipe } from \"effect\";\n\ntype McpMode = \"search\" | \"sync\";\n\ninterface RepoContextConfig {\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n}\n\ninterface RepoConfig {\n $schema?: string;\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n token?: string;\n serverUrl?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n}\n\ninterface ResolvedRepoConfig {\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n token?: string;\n serverUrl?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n configPath?: string;\n userConfigPath?: string;\n}\n\nconst CONFIG_FILENAME = \"braid.json\";\nconst USER_CONFIG_FILENAME = \"braid.user.json\";\n\nclass FileReadError extends Data.TaggedError(\"FileReadError\")<\n Record<string, never>\n> {}\n\nconst readJsonFile = <T>(path: string): Effect.Effect<T | null> =>\n pipe(\n Effect.tryPromise({\n try: () => readFile(path, \"utf-8\"),\n catch: () => new FileReadError({}),\n }),\n Effect.flatMap((content) =>\n pipe(\n Effect.try(() => JSON.parse(content) as T),\n Effect.orElseSucceed(() => null)\n )\n ),\n Effect.orElseSucceed(() => null)\n );\n\nconst fileExists = (path: string): Effect.Effect<boolean> =>\n pipe(\n Effect.tryPromise({\n try: () => readFile(path),\n catch: () => new FileReadError({}),\n }),\n Effect.map(() => true),\n Effect.orElseSucceed(() => false)\n );\n\nconst findConfigDir = (startDir: string): Effect.Effect<string | null> => {\n const search = (currentDir: string): Effect.Effect<string | null> => {\n const root = dirname(currentDir);\n if (currentDir === root) {\n return Effect.succeed(null);\n }\n\n const configPath = join(currentDir, CONFIG_FILENAME);\n const userConfigPath = join(currentDir, USER_CONFIG_FILENAME);\n\n return pipe(\n Effect.all([fileExists(configPath), fileExists(userConfigPath)]),\n Effect.flatMap(([hasConfig, hasUserConfig]) =>\n hasConfig || hasUserConfig\n ? Effect.succeed(currentDir)\n : search(dirname(currentDir))\n )\n );\n };\n\n return search(resolve(startDir));\n};\n\ninterface MergedConfigValues {\n orgProjects: string[] | undefined;\n personalProjects: string[] | undefined;\n profile: string | undefined;\n org: string | undefined;\n token: string | undefined;\n serverUrl: string | undefined;\n includeUserGlobalRules: boolean | undefined;\n includeOrgGlobalRules: boolean | undefined;\n mcpMode: McpMode | undefined;\n}\n\nconst mergeConfigValues = (\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null\n): MergedConfigValues => {\n return {\n orgProjects: userConfig?.orgProjects ?? baseConfig?.orgProjects,\n personalProjects:\n userConfig?.personalProjects ?? baseConfig?.personalProjects,\n profile: userConfig?.profile ?? baseConfig?.profile,\n org: userConfig?.org ?? baseConfig?.org,\n token: userConfig?.token ?? baseConfig?.token,\n serverUrl: userConfig?.serverUrl ?? baseConfig?.serverUrl,\n includeUserGlobalRules:\n userConfig?.includeUserGlobalRules ?? baseConfig?.includeUserGlobalRules,\n includeOrgGlobalRules:\n userConfig?.includeOrgGlobalRules ?? baseConfig?.includeOrgGlobalRules,\n mcpMode: userConfig?.mcpMode ?? baseConfig?.mcpMode,\n };\n};\n\nconst filterDefinedValues = (\n merged: MergedConfigValues,\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null,\n configPath: string,\n userConfigPath: string\n): ResolvedRepoConfig => {\n const result: ResolvedRepoConfig = {};\n\n if (merged.orgProjects !== undefined) {\n result.orgProjects = merged.orgProjects;\n }\n if (merged.personalProjects !== undefined) {\n result.personalProjects = merged.personalProjects;\n }\n if (merged.profile !== undefined) {\n result.profile = merged.profile;\n }\n if (merged.org !== undefined) {\n result.org = merged.org;\n }\n if (merged.token !== undefined) {\n result.token = merged.token;\n }\n if (merged.serverUrl !== undefined) {\n result.serverUrl = merged.serverUrl;\n }\n if (merged.includeUserGlobalRules !== undefined) {\n result.includeUserGlobalRules = merged.includeUserGlobalRules;\n }\n if (merged.includeOrgGlobalRules !== undefined) {\n result.includeOrgGlobalRules = merged.includeOrgGlobalRules;\n }\n if (merged.mcpMode !== undefined) {\n result.mcpMode = merged.mcpMode;\n }\n if (baseConfig) {\n result.configPath = configPath;\n }\n if (userConfig) {\n result.userConfigPath = userConfigPath;\n }\n\n return result;\n};\n\nconst buildResolvedConfig = (\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null,\n configPath: string,\n userConfigPath: string\n): ResolvedRepoConfig | null => {\n if (!(baseConfig || userConfig)) {\n return null;\n }\n\n const merged = mergeConfigValues(baseConfig, userConfig);\n return filterDefinedValues(\n merged,\n baseConfig,\n userConfig,\n configPath,\n userConfigPath\n );\n};\n\nconst loadRepoConfig = (\n startDir?: string\n): Effect.Effect<ResolvedRepoConfig | null> => {\n const searchDir = startDir ?? process.env.BRAID_CONFIG_DIR ?? process.cwd();\n\n return pipe(\n findConfigDir(searchDir),\n Effect.flatMap((configDir) => {\n if (!configDir) {\n return Effect.succeed(null);\n }\n\n const configPath = join(configDir, CONFIG_FILENAME);\n const userConfigPath = join(configDir, USER_CONFIG_FILENAME);\n\n return pipe(\n Effect.all({\n baseConfig: readJsonFile<RepoConfig>(configPath),\n userConfig: readJsonFile<RepoConfig>(userConfigPath),\n }),\n Effect.map(({ baseConfig, userConfig }) =>\n buildResolvedConfig(\n baseConfig,\n userConfig,\n configPath,\n userConfigPath\n )\n )\n );\n })\n );\n};\n\nconst loadRepoConfigAsync = (\n startDir?: string\n): Promise<ResolvedRepoConfig | null> =>\n Effect.runPromise(loadRepoConfig(startDir));\n\nconst hasContextOverrides = (config: RepoContextConfig): boolean =>\n Boolean(\n config.orgProjects?.length ||\n config.personalProjects?.length ||\n config.profile ||\n config.org\n );\n\nexport type { McpMode, RepoConfig, RepoContextConfig, ResolvedRepoConfig };\nexport { hasContextOverrides, loadRepoConfig, loadRepoConfigAsync };\n","import { startProxyFromEnv } from \"./proxy.ts\";\n\nstartProxyFromEnv().catch(() => undefined);\n"],"mappings":"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","names":["process","Data","Effect","pipe","Data","Effect","pipe","process"]}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as effect_Cause from 'effect/Cause';
|
|
2
|
+
import * as effect_Types from 'effect/Types';
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { Effect } from 'effect';
|
|
6
|
+
|
|
7
|
+
type McpMode = "search" | "sync";
|
|
8
|
+
interface RepoContextConfig {
|
|
9
|
+
orgProjects?: string[];
|
|
10
|
+
personalProjects?: string[];
|
|
11
|
+
profile?: string;
|
|
12
|
+
org?: string;
|
|
13
|
+
includeUserGlobalRules?: boolean;
|
|
14
|
+
includeOrgGlobalRules?: boolean;
|
|
15
|
+
mcpMode?: McpMode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ProxyConfig {
|
|
19
|
+
token: string;
|
|
20
|
+
serverUrl?: string | undefined;
|
|
21
|
+
repoContext?: RepoContextConfig | undefined;
|
|
22
|
+
}
|
|
23
|
+
declare const ProxyError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
|
|
24
|
+
readonly _tag: "ProxyError";
|
|
25
|
+
} & Readonly<A>;
|
|
26
|
+
declare class ProxyError extends ProxyError_base<{
|
|
27
|
+
code: "missing_token" | "connection_error" | "runtime_error";
|
|
28
|
+
message: string;
|
|
29
|
+
}> {
|
|
30
|
+
}
|
|
31
|
+
declare const createProxyServer: (config: ProxyConfig) => Effect.Effect<{
|
|
32
|
+
server: Server;
|
|
33
|
+
client: Client;
|
|
34
|
+
close: () => Promise<void>;
|
|
35
|
+
}, ProxyError>;
|
|
36
|
+
declare const startProxyFromEnv: () => Promise<void>;
|
|
37
|
+
|
|
38
|
+
export { type ProxyConfig, createProxyServer, startProxyFromEnv };
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/proxy.ts
|
|
4
|
+
import process2 from "process";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema
|
|
12
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { Data as Data2, Effect as Effect2, pipe as pipe2 } from "effect";
|
|
14
|
+
|
|
15
|
+
// src/config.ts
|
|
16
|
+
import { readFile } from "fs/promises";
|
|
17
|
+
import { dirname, join, resolve } from "path";
|
|
18
|
+
import process from "process";
|
|
19
|
+
import { Data, Effect, pipe } from "effect";
|
|
20
|
+
var CONFIG_FILENAME = "braid.json";
|
|
21
|
+
var USER_CONFIG_FILENAME = "braid.user.json";
|
|
22
|
+
var FileReadError = class extends Data.TaggedError("FileReadError") {
|
|
23
|
+
};
|
|
24
|
+
var readJsonFile = (path) => pipe(
|
|
25
|
+
Effect.tryPromise({
|
|
26
|
+
try: () => readFile(path, "utf-8"),
|
|
27
|
+
catch: () => new FileReadError({})
|
|
28
|
+
}),
|
|
29
|
+
Effect.flatMap(
|
|
30
|
+
(content) => pipe(
|
|
31
|
+
Effect.try(() => JSON.parse(content)),
|
|
32
|
+
Effect.orElseSucceed(() => null)
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
Effect.orElseSucceed(() => null)
|
|
36
|
+
);
|
|
37
|
+
var fileExists = (path) => pipe(
|
|
38
|
+
Effect.tryPromise({
|
|
39
|
+
try: () => readFile(path),
|
|
40
|
+
catch: () => new FileReadError({})
|
|
41
|
+
}),
|
|
42
|
+
Effect.map(() => true),
|
|
43
|
+
Effect.orElseSucceed(() => false)
|
|
44
|
+
);
|
|
45
|
+
var findConfigDir = (startDir) => {
|
|
46
|
+
const search = (currentDir) => {
|
|
47
|
+
const root = dirname(currentDir);
|
|
48
|
+
if (currentDir === root) {
|
|
49
|
+
return Effect.succeed(null);
|
|
50
|
+
}
|
|
51
|
+
const configPath = join(currentDir, CONFIG_FILENAME);
|
|
52
|
+
const userConfigPath = join(currentDir, USER_CONFIG_FILENAME);
|
|
53
|
+
return pipe(
|
|
54
|
+
Effect.all([fileExists(configPath), fileExists(userConfigPath)]),
|
|
55
|
+
Effect.flatMap(
|
|
56
|
+
([hasConfig, hasUserConfig]) => hasConfig || hasUserConfig ? Effect.succeed(currentDir) : search(dirname(currentDir))
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
return search(resolve(startDir));
|
|
61
|
+
};
|
|
62
|
+
var mergeConfigValues = (baseConfig, userConfig) => {
|
|
63
|
+
return {
|
|
64
|
+
orgProjects: userConfig?.orgProjects ?? baseConfig?.orgProjects,
|
|
65
|
+
personalProjects: userConfig?.personalProjects ?? baseConfig?.personalProjects,
|
|
66
|
+
profile: userConfig?.profile ?? baseConfig?.profile,
|
|
67
|
+
org: userConfig?.org ?? baseConfig?.org,
|
|
68
|
+
token: userConfig?.token ?? baseConfig?.token,
|
|
69
|
+
serverUrl: userConfig?.serverUrl ?? baseConfig?.serverUrl,
|
|
70
|
+
includeUserGlobalRules: userConfig?.includeUserGlobalRules ?? baseConfig?.includeUserGlobalRules,
|
|
71
|
+
includeOrgGlobalRules: userConfig?.includeOrgGlobalRules ?? baseConfig?.includeOrgGlobalRules,
|
|
72
|
+
mcpMode: userConfig?.mcpMode ?? baseConfig?.mcpMode
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
var filterDefinedValues = (merged, baseConfig, userConfig, configPath, userConfigPath) => {
|
|
76
|
+
const result = {};
|
|
77
|
+
if (merged.orgProjects !== void 0) {
|
|
78
|
+
result.orgProjects = merged.orgProjects;
|
|
79
|
+
}
|
|
80
|
+
if (merged.personalProjects !== void 0) {
|
|
81
|
+
result.personalProjects = merged.personalProjects;
|
|
82
|
+
}
|
|
83
|
+
if (merged.profile !== void 0) {
|
|
84
|
+
result.profile = merged.profile;
|
|
85
|
+
}
|
|
86
|
+
if (merged.org !== void 0) {
|
|
87
|
+
result.org = merged.org;
|
|
88
|
+
}
|
|
89
|
+
if (merged.token !== void 0) {
|
|
90
|
+
result.token = merged.token;
|
|
91
|
+
}
|
|
92
|
+
if (merged.serverUrl !== void 0) {
|
|
93
|
+
result.serverUrl = merged.serverUrl;
|
|
94
|
+
}
|
|
95
|
+
if (merged.includeUserGlobalRules !== void 0) {
|
|
96
|
+
result.includeUserGlobalRules = merged.includeUserGlobalRules;
|
|
97
|
+
}
|
|
98
|
+
if (merged.includeOrgGlobalRules !== void 0) {
|
|
99
|
+
result.includeOrgGlobalRules = merged.includeOrgGlobalRules;
|
|
100
|
+
}
|
|
101
|
+
if (merged.mcpMode !== void 0) {
|
|
102
|
+
result.mcpMode = merged.mcpMode;
|
|
103
|
+
}
|
|
104
|
+
if (baseConfig) {
|
|
105
|
+
result.configPath = configPath;
|
|
106
|
+
}
|
|
107
|
+
if (userConfig) {
|
|
108
|
+
result.userConfigPath = userConfigPath;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
};
|
|
112
|
+
var buildResolvedConfig = (baseConfig, userConfig, configPath, userConfigPath) => {
|
|
113
|
+
if (!(baseConfig || userConfig)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const merged = mergeConfigValues(baseConfig, userConfig);
|
|
117
|
+
return filterDefinedValues(
|
|
118
|
+
merged,
|
|
119
|
+
baseConfig,
|
|
120
|
+
userConfig,
|
|
121
|
+
configPath,
|
|
122
|
+
userConfigPath
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
var loadRepoConfig = (startDir) => {
|
|
126
|
+
const searchDir = startDir ?? process.env.BRAID_CONFIG_DIR ?? process.cwd();
|
|
127
|
+
return pipe(
|
|
128
|
+
findConfigDir(searchDir),
|
|
129
|
+
Effect.flatMap((configDir) => {
|
|
130
|
+
if (!configDir) {
|
|
131
|
+
return Effect.succeed(null);
|
|
132
|
+
}
|
|
133
|
+
const configPath = join(configDir, CONFIG_FILENAME);
|
|
134
|
+
const userConfigPath = join(configDir, USER_CONFIG_FILENAME);
|
|
135
|
+
return pipe(
|
|
136
|
+
Effect.all({
|
|
137
|
+
baseConfig: readJsonFile(configPath),
|
|
138
|
+
userConfig: readJsonFile(userConfigPath)
|
|
139
|
+
}),
|
|
140
|
+
Effect.map(
|
|
141
|
+
({ baseConfig, userConfig }) => buildResolvedConfig(
|
|
142
|
+
baseConfig,
|
|
143
|
+
userConfig,
|
|
144
|
+
configPath,
|
|
145
|
+
userConfigPath
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
var hasContextOverrides = (config) => Boolean(
|
|
153
|
+
config.orgProjects?.length || config.personalProjects?.length || config.profile || config.org
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// src/proxy.ts
|
|
157
|
+
var ProxyError = class extends Data2.TaggedError("ProxyError") {
|
|
158
|
+
};
|
|
159
|
+
var DEFAULT_SERVER_URL = "https://app.braid.cloud/api/mcp";
|
|
160
|
+
var createMcpClient = (serverUrl, token, mcpMode) => Effect2.tryPromise({
|
|
161
|
+
try: async () => {
|
|
162
|
+
const headers = {
|
|
163
|
+
Authorization: `Bearer ${token}`
|
|
164
|
+
};
|
|
165
|
+
if (mcpMode) {
|
|
166
|
+
headers["X-Braid-Mcp-Mode"] = mcpMode;
|
|
167
|
+
}
|
|
168
|
+
const clientTransport = new StreamableHTTPClientTransport(
|
|
169
|
+
new URL(serverUrl),
|
|
170
|
+
{
|
|
171
|
+
requestInit: {
|
|
172
|
+
headers
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
const client = new Client(
|
|
177
|
+
{ name: "braid-mcp-proxy", version: "0.1.0" },
|
|
178
|
+
{ capabilities: {} }
|
|
179
|
+
);
|
|
180
|
+
await client.connect(clientTransport);
|
|
181
|
+
return client;
|
|
182
|
+
},
|
|
183
|
+
catch: (e) => new ProxyError({
|
|
184
|
+
code: "connection_error",
|
|
185
|
+
message: e instanceof Error ? e.message : "Failed to connect to server"
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
var injectContext = (args, repoContext) => {
|
|
189
|
+
if (!(repoContext && hasContextOverrides(repoContext))) {
|
|
190
|
+
return args;
|
|
191
|
+
}
|
|
192
|
+
const result = { ...args };
|
|
193
|
+
if (repoContext.orgProjects !== void 0 && args.orgProjects === void 0) {
|
|
194
|
+
result.orgProjects = repoContext.orgProjects;
|
|
195
|
+
}
|
|
196
|
+
if (repoContext.personalProjects !== void 0 && args.personalProjects === void 0) {
|
|
197
|
+
result.personalProjects = repoContext.personalProjects;
|
|
198
|
+
}
|
|
199
|
+
if (repoContext.profile !== void 0 && args.profile === void 0) {
|
|
200
|
+
result.profile = repoContext.profile;
|
|
201
|
+
}
|
|
202
|
+
if (repoContext.org !== void 0 && args.org === void 0) {
|
|
203
|
+
result.org = repoContext.org;
|
|
204
|
+
}
|
|
205
|
+
if (repoContext.includeUserGlobalRules !== void 0 && args.includeUserGlobalRules === void 0) {
|
|
206
|
+
result.includeUserGlobalRules = repoContext.includeUserGlobalRules;
|
|
207
|
+
}
|
|
208
|
+
if (repoContext.includeOrgGlobalRules !== void 0 && args.includeOrgGlobalRules === void 0) {
|
|
209
|
+
result.includeOrgGlobalRules = repoContext.includeOrgGlobalRules;
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
};
|
|
213
|
+
var createMcpServer = (client, repoContext) => Effect2.tryPromise({
|
|
214
|
+
try: async () => {
|
|
215
|
+
const serverTransport = new StdioServerTransport();
|
|
216
|
+
const server = new Server(
|
|
217
|
+
{ name: "braid", version: "0.1.0" },
|
|
218
|
+
{ capabilities: { tools: {} } }
|
|
219
|
+
);
|
|
220
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
221
|
+
const result = await client.listTools();
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
225
|
+
const injectedArgs = injectContext(
|
|
226
|
+
request.params.arguments ?? {},
|
|
227
|
+
repoContext
|
|
228
|
+
);
|
|
229
|
+
const result = await client.callTool({
|
|
230
|
+
name: request.params.name,
|
|
231
|
+
arguments: injectedArgs
|
|
232
|
+
});
|
|
233
|
+
return result;
|
|
234
|
+
});
|
|
235
|
+
await server.connect(serverTransport);
|
|
236
|
+
return server;
|
|
237
|
+
},
|
|
238
|
+
catch: (e) => new ProxyError({
|
|
239
|
+
code: "runtime_error",
|
|
240
|
+
message: e instanceof Error ? e.message : "Failed to create server"
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
var createProxyServer = (config) => {
|
|
244
|
+
const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL;
|
|
245
|
+
const mcpMode = config.repoContext?.mcpMode;
|
|
246
|
+
return pipe2(
|
|
247
|
+
createMcpClient(serverUrl, config.token, mcpMode),
|
|
248
|
+
Effect2.flatMap(
|
|
249
|
+
(client) => pipe2(
|
|
250
|
+
createMcpServer(client, config.repoContext),
|
|
251
|
+
Effect2.map((server) => {
|
|
252
|
+
const close = async () => {
|
|
253
|
+
await server.close();
|
|
254
|
+
await client.close();
|
|
255
|
+
};
|
|
256
|
+
return { server, client, close };
|
|
257
|
+
})
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
var buildRepoContext = (repoConfig) => {
|
|
263
|
+
if (!repoConfig) {
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
const context = {};
|
|
267
|
+
if (repoConfig.orgProjects !== void 0) {
|
|
268
|
+
context.orgProjects = repoConfig.orgProjects;
|
|
269
|
+
}
|
|
270
|
+
if (repoConfig.personalProjects !== void 0) {
|
|
271
|
+
context.personalProjects = repoConfig.personalProjects;
|
|
272
|
+
}
|
|
273
|
+
if (repoConfig.profile !== void 0) {
|
|
274
|
+
context.profile = repoConfig.profile;
|
|
275
|
+
}
|
|
276
|
+
if (repoConfig.org !== void 0) {
|
|
277
|
+
context.org = repoConfig.org;
|
|
278
|
+
}
|
|
279
|
+
if (repoConfig.includeUserGlobalRules !== void 0) {
|
|
280
|
+
context.includeUserGlobalRules = repoConfig.includeUserGlobalRules;
|
|
281
|
+
}
|
|
282
|
+
if (repoConfig.includeOrgGlobalRules !== void 0) {
|
|
283
|
+
context.includeOrgGlobalRules = repoConfig.includeOrgGlobalRules;
|
|
284
|
+
}
|
|
285
|
+
if (repoConfig.mcpMode !== void 0) {
|
|
286
|
+
context.mcpMode = repoConfig.mcpMode;
|
|
287
|
+
}
|
|
288
|
+
return Object.keys(context).length > 0 ? context : void 0;
|
|
289
|
+
};
|
|
290
|
+
var logConfigPaths = (repoConfig) => {
|
|
291
|
+
if (repoConfig?.configPath) {
|
|
292
|
+
process2.stderr.write(`Loaded config from ${repoConfig.configPath}
|
|
293
|
+
`);
|
|
294
|
+
}
|
|
295
|
+
if (repoConfig?.userConfigPath) {
|
|
296
|
+
process2.stderr.write(
|
|
297
|
+
`Loaded user config from ${repoConfig.userConfigPath}
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var resolveConfig = () => pipe2(
|
|
303
|
+
loadRepoConfig(),
|
|
304
|
+
Effect2.flatMap((repoConfig) => {
|
|
305
|
+
const token = repoConfig?.token ?? process2.env.BRAID_TOKEN;
|
|
306
|
+
if (!token) {
|
|
307
|
+
return Effect2.fail(
|
|
308
|
+
new ProxyError({
|
|
309
|
+
code: "missing_token",
|
|
310
|
+
message: "BRAID_TOKEN environment variable is required"
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const serverUrl = repoConfig?.serverUrl ?? process2.env.BRAID_MCP_URL;
|
|
315
|
+
logConfigPaths(repoConfig);
|
|
316
|
+
return Effect2.succeed({
|
|
317
|
+
token,
|
|
318
|
+
serverUrl,
|
|
319
|
+
repoContext: buildRepoContext(repoConfig)
|
|
320
|
+
});
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
var startProxy = () => pipe2(
|
|
324
|
+
resolveConfig(),
|
|
325
|
+
Effect2.flatMap(createProxyServer),
|
|
326
|
+
Effect2.flatMap(
|
|
327
|
+
({ close }) => Effect2.async(() => {
|
|
328
|
+
const shutdown = async () => {
|
|
329
|
+
await close();
|
|
330
|
+
process2.exit(0);
|
|
331
|
+
};
|
|
332
|
+
process2.on("SIGINT", shutdown);
|
|
333
|
+
process2.on("SIGTERM", shutdown);
|
|
334
|
+
process2.stdin.resume();
|
|
335
|
+
})
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
var startProxyFromEnv = async () => {
|
|
339
|
+
await Effect2.runPromise(
|
|
340
|
+
pipe2(
|
|
341
|
+
startProxy(),
|
|
342
|
+
Effect2.catchAll((error) => {
|
|
343
|
+
process2.stderr.write(`Error starting proxy: ${error.message}
|
|
344
|
+
`);
|
|
345
|
+
return process2.exit(1);
|
|
346
|
+
})
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
export {
|
|
351
|
+
createProxyServer,
|
|
352
|
+
startProxyFromEnv
|
|
353
|
+
};
|
|
354
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts","../src/config.ts"],"sourcesContent":["import process from \"node:process\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Data, Effect, pipe } from \"effect\";\nimport type { RepoContextConfig, ResolvedRepoConfig } from \"./config.ts\";\nimport { hasContextOverrides, loadRepoConfig } from \"./config.ts\";\n\ninterface ProxyConfig {\n token: string;\n serverUrl?: string | undefined;\n repoContext?: RepoContextConfig | undefined;\n}\n\nclass ProxyError extends Data.TaggedError(\"ProxyError\")<{\n code: \"missing_token\" | \"connection_error\" | \"runtime_error\";\n message: string;\n}> {}\n\nconst DEFAULT_SERVER_URL = \"https://app.braid.cloud/api/mcp\";\n\nconst createMcpClient = (\n serverUrl: string,\n token: string,\n mcpMode?: string\n): Effect.Effect<Client, ProxyError> =>\n Effect.tryPromise({\n try: async () => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n };\n\n if (mcpMode) {\n headers[\"X-Braid-Mcp-Mode\"] = mcpMode;\n }\n\n const clientTransport = new StreamableHTTPClientTransport(\n new URL(serverUrl),\n {\n requestInit: {\n headers,\n },\n }\n );\n\n const client = new Client(\n { name: \"braid-mcp-proxy\", version: \"0.1.0\" },\n { capabilities: {} }\n );\n\n await client.connect(clientTransport as Transport);\n return client;\n },\n catch: (e) =>\n new ProxyError({\n code: \"connection_error\",\n message: e instanceof Error ? e.message : \"Failed to connect to server\",\n }),\n });\n\nconst injectContext = (\n args: Record<string, unknown>,\n repoContext: RepoContextConfig | undefined\n): Record<string, unknown> => {\n if (!(repoContext && hasContextOverrides(repoContext))) {\n return args;\n }\n\n const result = { ...args };\n\n if (repoContext.orgProjects !== undefined && args.orgProjects === undefined) {\n result.orgProjects = repoContext.orgProjects;\n }\n if (\n repoContext.personalProjects !== undefined &&\n args.personalProjects === undefined\n ) {\n result.personalProjects = repoContext.personalProjects;\n }\n if (repoContext.profile !== undefined && args.profile === undefined) {\n result.profile = repoContext.profile;\n }\n if (repoContext.org !== undefined && args.org === undefined) {\n result.org = repoContext.org;\n }\n if (\n repoContext.includeUserGlobalRules !== undefined &&\n args.includeUserGlobalRules === undefined\n ) {\n result.includeUserGlobalRules = repoContext.includeUserGlobalRules;\n }\n if (\n repoContext.includeOrgGlobalRules !== undefined &&\n args.includeOrgGlobalRules === undefined\n ) {\n result.includeOrgGlobalRules = repoContext.includeOrgGlobalRules;\n }\n\n return result;\n};\n\nconst createMcpServer = (\n client: Client,\n repoContext: RepoContextConfig | undefined\n): Effect.Effect<Server, ProxyError> =>\n Effect.tryPromise({\n try: async () => {\n const serverTransport = new StdioServerTransport();\n\n const server = new Server(\n { name: \"braid\", version: \"0.1.0\" },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const result = await client.listTools();\n return result;\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const injectedArgs = injectContext(\n request.params.arguments ?? {},\n repoContext\n );\n const result = await client.callTool({\n name: request.params.name,\n arguments: injectedArgs,\n });\n return result;\n });\n\n await server.connect(serverTransport);\n return server;\n },\n catch: (e) =>\n new ProxyError({\n code: \"runtime_error\",\n message: e instanceof Error ? e.message : \"Failed to create server\",\n }),\n });\n\nconst createProxyServer = (\n config: ProxyConfig\n): Effect.Effect<\n { server: Server; client: Client; close: () => Promise<void> },\n ProxyError\n> => {\n const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL;\n const mcpMode = config.repoContext?.mcpMode;\n\n return pipe(\n createMcpClient(serverUrl, config.token, mcpMode),\n Effect.flatMap((client) =>\n pipe(\n createMcpServer(client, config.repoContext),\n Effect.map((server) => {\n const close = async () => {\n await server.close();\n await client.close();\n };\n return { server, client, close };\n })\n )\n )\n );\n};\n\nconst buildRepoContext = (\n repoConfig: ResolvedRepoConfig | null\n): RepoContextConfig | undefined => {\n if (!repoConfig) {\n return undefined;\n }\n\n const context: RepoContextConfig = {};\n\n if (repoConfig.orgProjects !== undefined) {\n context.orgProjects = repoConfig.orgProjects;\n }\n if (repoConfig.personalProjects !== undefined) {\n context.personalProjects = repoConfig.personalProjects;\n }\n if (repoConfig.profile !== undefined) {\n context.profile = repoConfig.profile;\n }\n if (repoConfig.org !== undefined) {\n context.org = repoConfig.org;\n }\n if (repoConfig.includeUserGlobalRules !== undefined) {\n context.includeUserGlobalRules = repoConfig.includeUserGlobalRules;\n }\n if (repoConfig.includeOrgGlobalRules !== undefined) {\n context.includeOrgGlobalRules = repoConfig.includeOrgGlobalRules;\n }\n if (repoConfig.mcpMode !== undefined) {\n context.mcpMode = repoConfig.mcpMode;\n }\n\n return Object.keys(context).length > 0 ? context : undefined;\n};\n\nconst logConfigPaths = (\n repoConfig: {\n configPath?: string;\n userConfigPath?: string;\n mcpMode?: string;\n } | null\n): void => {\n if (repoConfig?.configPath) {\n process.stderr.write(`Loaded config from ${repoConfig.configPath}\\n`);\n }\n if (repoConfig?.userConfigPath) {\n process.stderr.write(\n `Loaded user config from ${repoConfig.userConfigPath}\\n`\n );\n }\n};\n\nconst resolveConfig = (): Effect.Effect<ProxyConfig, ProxyError> =>\n pipe(\n loadRepoConfig(),\n Effect.flatMap((repoConfig) => {\n const token = repoConfig?.token ?? process.env.BRAID_TOKEN;\n if (!token) {\n return Effect.fail(\n new ProxyError({\n code: \"missing_token\",\n message: \"BRAID_TOKEN environment variable is required\",\n })\n );\n }\n\n const serverUrl = repoConfig?.serverUrl ?? process.env.BRAID_MCP_URL;\n logConfigPaths(repoConfig);\n\n return Effect.succeed({\n token,\n serverUrl,\n repoContext: buildRepoContext(repoConfig),\n });\n })\n );\n\nconst startProxy = (): Effect.Effect<void, ProxyError> =>\n pipe(\n resolveConfig(),\n Effect.flatMap(createProxyServer),\n Effect.flatMap(({ close }) =>\n Effect.async<void, ProxyError>(() => {\n const shutdown = async () => {\n await close();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n process.stdin.resume();\n })\n )\n );\n\nconst startProxyFromEnv = async (): Promise<void> => {\n await Effect.runPromise(\n pipe(\n startProxy(),\n Effect.catchAll((error): Effect.Effect<never> => {\n process.stderr.write(`Error starting proxy: ${error.message}\\n`);\n return process.exit(1) as never;\n })\n )\n );\n};\n\nexport type { ProxyConfig };\nexport { createProxyServer, startProxyFromEnv };\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { Data, Effect, pipe } from \"effect\";\n\ntype McpMode = \"search\" | \"sync\";\n\ninterface RepoContextConfig {\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n}\n\ninterface RepoConfig {\n $schema?: string;\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n token?: string;\n serverUrl?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n}\n\ninterface ResolvedRepoConfig {\n orgProjects?: string[];\n personalProjects?: string[];\n profile?: string;\n org?: string;\n token?: string;\n serverUrl?: string;\n includeUserGlobalRules?: boolean;\n includeOrgGlobalRules?: boolean;\n mcpMode?: McpMode;\n configPath?: string;\n userConfigPath?: string;\n}\n\nconst CONFIG_FILENAME = \"braid.json\";\nconst USER_CONFIG_FILENAME = \"braid.user.json\";\n\nclass FileReadError extends Data.TaggedError(\"FileReadError\")<\n Record<string, never>\n> {}\n\nconst readJsonFile = <T>(path: string): Effect.Effect<T | null> =>\n pipe(\n Effect.tryPromise({\n try: () => readFile(path, \"utf-8\"),\n catch: () => new FileReadError({}),\n }),\n Effect.flatMap((content) =>\n pipe(\n Effect.try(() => JSON.parse(content) as T),\n Effect.orElseSucceed(() => null)\n )\n ),\n Effect.orElseSucceed(() => null)\n );\n\nconst fileExists = (path: string): Effect.Effect<boolean> =>\n pipe(\n Effect.tryPromise({\n try: () => readFile(path),\n catch: () => new FileReadError({}),\n }),\n Effect.map(() => true),\n Effect.orElseSucceed(() => false)\n );\n\nconst findConfigDir = (startDir: string): Effect.Effect<string | null> => {\n const search = (currentDir: string): Effect.Effect<string | null> => {\n const root = dirname(currentDir);\n if (currentDir === root) {\n return Effect.succeed(null);\n }\n\n const configPath = join(currentDir, CONFIG_FILENAME);\n const userConfigPath = join(currentDir, USER_CONFIG_FILENAME);\n\n return pipe(\n Effect.all([fileExists(configPath), fileExists(userConfigPath)]),\n Effect.flatMap(([hasConfig, hasUserConfig]) =>\n hasConfig || hasUserConfig\n ? Effect.succeed(currentDir)\n : search(dirname(currentDir))\n )\n );\n };\n\n return search(resolve(startDir));\n};\n\ninterface MergedConfigValues {\n orgProjects: string[] | undefined;\n personalProjects: string[] | undefined;\n profile: string | undefined;\n org: string | undefined;\n token: string | undefined;\n serverUrl: string | undefined;\n includeUserGlobalRules: boolean | undefined;\n includeOrgGlobalRules: boolean | undefined;\n mcpMode: McpMode | undefined;\n}\n\nconst mergeConfigValues = (\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null\n): MergedConfigValues => {\n return {\n orgProjects: userConfig?.orgProjects ?? baseConfig?.orgProjects,\n personalProjects:\n userConfig?.personalProjects ?? baseConfig?.personalProjects,\n profile: userConfig?.profile ?? baseConfig?.profile,\n org: userConfig?.org ?? baseConfig?.org,\n token: userConfig?.token ?? baseConfig?.token,\n serverUrl: userConfig?.serverUrl ?? baseConfig?.serverUrl,\n includeUserGlobalRules:\n userConfig?.includeUserGlobalRules ?? baseConfig?.includeUserGlobalRules,\n includeOrgGlobalRules:\n userConfig?.includeOrgGlobalRules ?? baseConfig?.includeOrgGlobalRules,\n mcpMode: userConfig?.mcpMode ?? baseConfig?.mcpMode,\n };\n};\n\nconst filterDefinedValues = (\n merged: MergedConfigValues,\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null,\n configPath: string,\n userConfigPath: string\n): ResolvedRepoConfig => {\n const result: ResolvedRepoConfig = {};\n\n if (merged.orgProjects !== undefined) {\n result.orgProjects = merged.orgProjects;\n }\n if (merged.personalProjects !== undefined) {\n result.personalProjects = merged.personalProjects;\n }\n if (merged.profile !== undefined) {\n result.profile = merged.profile;\n }\n if (merged.org !== undefined) {\n result.org = merged.org;\n }\n if (merged.token !== undefined) {\n result.token = merged.token;\n }\n if (merged.serverUrl !== undefined) {\n result.serverUrl = merged.serverUrl;\n }\n if (merged.includeUserGlobalRules !== undefined) {\n result.includeUserGlobalRules = merged.includeUserGlobalRules;\n }\n if (merged.includeOrgGlobalRules !== undefined) {\n result.includeOrgGlobalRules = merged.includeOrgGlobalRules;\n }\n if (merged.mcpMode !== undefined) {\n result.mcpMode = merged.mcpMode;\n }\n if (baseConfig) {\n result.configPath = configPath;\n }\n if (userConfig) {\n result.userConfigPath = userConfigPath;\n }\n\n return result;\n};\n\nconst buildResolvedConfig = (\n baseConfig: RepoConfig | null,\n userConfig: RepoConfig | null,\n configPath: string,\n userConfigPath: string\n): ResolvedRepoConfig | null => {\n if (!(baseConfig || userConfig)) {\n return null;\n }\n\n const merged = mergeConfigValues(baseConfig, userConfig);\n return filterDefinedValues(\n merged,\n baseConfig,\n userConfig,\n configPath,\n userConfigPath\n );\n};\n\nconst loadRepoConfig = (\n startDir?: string\n): Effect.Effect<ResolvedRepoConfig | null> => {\n const searchDir = startDir ?? process.env.BRAID_CONFIG_DIR ?? process.cwd();\n\n return pipe(\n findConfigDir(searchDir),\n Effect.flatMap((configDir) => {\n if (!configDir) {\n return Effect.succeed(null);\n }\n\n const configPath = join(configDir, CONFIG_FILENAME);\n const userConfigPath = join(configDir, USER_CONFIG_FILENAME);\n\n return pipe(\n Effect.all({\n baseConfig: readJsonFile<RepoConfig>(configPath),\n userConfig: readJsonFile<RepoConfig>(userConfigPath),\n }),\n Effect.map(({ baseConfig, userConfig }) =>\n buildResolvedConfig(\n baseConfig,\n userConfig,\n configPath,\n userConfigPath\n )\n )\n );\n })\n );\n};\n\nconst loadRepoConfigAsync = (\n startDir?: string\n): Promise<ResolvedRepoConfig | null> =>\n Effect.runPromise(loadRepoConfig(startDir));\n\nconst hasContextOverrides = (config: RepoContextConfig): boolean =>\n Boolean(\n config.orgProjects?.length ||\n config.personalProjects?.length ||\n config.profile ||\n config.org\n );\n\nexport type { McpMode, RepoConfig, RepoContextConfig, ResolvedRepoConfig };\nexport { hasContextOverrides, loadRepoConfig, loadRepoConfigAsync };\n"],"mappings":";;;AAAA,OAAOA,cAAa;AACpB,SAAS,cAAc;AACvB,SAAS,qCAAqC;AAC9C,SAAS,cAAc;AACvB,SAAS,4BAA4B;AAErC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,QAAAC,OAAM,UAAAC,SAAQ,QAAAC,aAAY;;;ACVnC,SAAS,gBAAgB;AACzB,SAAS,SAAS,MAAM,eAAe;AACvC,OAAO,aAAa;AACpB,SAAS,MAAM,QAAQ,YAAY;AAyCnC,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAE7B,IAAM,gBAAN,cAA4B,KAAK,YAAY,eAAe,EAE1D;AAAC;AAEH,IAAM,eAAe,CAAI,SACvB;AAAA,EACE,OAAO,WAAW;AAAA,IAChB,KAAK,MAAM,SAAS,MAAM,OAAO;AAAA,IACjC,OAAO,MAAM,IAAI,cAAc,CAAC,CAAC;AAAA,EACnC,CAAC;AAAA,EACD,OAAO;AAAA,IAAQ,CAAC,YACd;AAAA,MACE,OAAO,IAAI,MAAM,KAAK,MAAM,OAAO,CAAM;AAAA,MACzC,OAAO,cAAc,MAAM,IAAI;AAAA,IACjC;AAAA,EACF;AAAA,EACA,OAAO,cAAc,MAAM,IAAI;AACjC;AAEF,IAAM,aAAa,CAAC,SAClB;AAAA,EACE,OAAO,WAAW;AAAA,IAChB,KAAK,MAAM,SAAS,IAAI;AAAA,IACxB,OAAO,MAAM,IAAI,cAAc,CAAC,CAAC;AAAA,EACnC,CAAC;AAAA,EACD,OAAO,IAAI,MAAM,IAAI;AAAA,EACrB,OAAO,cAAc,MAAM,KAAK;AAClC;AAEF,IAAM,gBAAgB,CAAC,aAAmD;AACxE,QAAM,SAAS,CAAC,eAAqD;AACnE,UAAM,OAAO,QAAQ,UAAU;AAC/B,QAAI,eAAe,MAAM;AACvB,aAAO,OAAO,QAAQ,IAAI;AAAA,IAC5B;AAEA,UAAM,aAAa,KAAK,YAAY,eAAe;AACnD,UAAM,iBAAiB,KAAK,YAAY,oBAAoB;AAE5D,WAAO;AAAA,MACL,OAAO,IAAI,CAAC,WAAW,UAAU,GAAG,WAAW,cAAc,CAAC,CAAC;AAAA,MAC/D,OAAO;AAAA,QAAQ,CAAC,CAAC,WAAW,aAAa,MACvC,aAAa,gBACT,OAAO,QAAQ,UAAU,IACzB,OAAO,QAAQ,UAAU,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO,QAAQ,QAAQ,CAAC;AACjC;AAcA,IAAM,oBAAoB,CACxB,YACA,eACuB;AACvB,SAAO;AAAA,IACL,aAAa,YAAY,eAAe,YAAY;AAAA,IACpD,kBACE,YAAY,oBAAoB,YAAY;AAAA,IAC9C,SAAS,YAAY,WAAW,YAAY;AAAA,IAC5C,KAAK,YAAY,OAAO,YAAY;AAAA,IACpC,OAAO,YAAY,SAAS,YAAY;AAAA,IACxC,WAAW,YAAY,aAAa,YAAY;AAAA,IAChD,wBACE,YAAY,0BAA0B,YAAY;AAAA,IACpD,uBACE,YAAY,yBAAyB,YAAY;AAAA,IACnD,SAAS,YAAY,WAAW,YAAY;AAAA,EAC9C;AACF;AAEA,IAAM,sBAAsB,CAC1B,QACA,YACA,YACA,YACA,mBACuB;AACvB,QAAM,SAA6B,CAAC;AAEpC,MAAI,OAAO,gBAAgB,QAAW;AACpC,WAAO,cAAc,OAAO;AAAA,EAC9B;AACA,MAAI,OAAO,qBAAqB,QAAW;AACzC,WAAO,mBAAmB,OAAO;AAAA,EACnC;AACA,MAAI,OAAO,YAAY,QAAW;AAChC,WAAO,UAAU,OAAO;AAAA,EAC1B;AACA,MAAI,OAAO,QAAQ,QAAW;AAC5B,WAAO,MAAM,OAAO;AAAA,EACtB;AACA,MAAI,OAAO,UAAU,QAAW;AAC9B,WAAO,QAAQ,OAAO;AAAA,EACxB;AACA,MAAI,OAAO,cAAc,QAAW;AAClC,WAAO,YAAY,OAAO;AAAA,EAC5B;AACA,MAAI,OAAO,2BAA2B,QAAW;AAC/C,WAAO,yBAAyB,OAAO;AAAA,EACzC;AACA,MAAI,OAAO,0BAA0B,QAAW;AAC9C,WAAO,wBAAwB,OAAO;AAAA,EACxC;AACA,MAAI,OAAO,YAAY,QAAW;AAChC,WAAO,UAAU,OAAO;AAAA,EAC1B;AACA,MAAI,YAAY;AACd,WAAO,aAAa;AAAA,EACtB;AACA,MAAI,YAAY;AACd,WAAO,iBAAiB;AAAA,EAC1B;AAEA,SAAO;AACT;AAEA,IAAM,sBAAsB,CAC1B,YACA,YACA,YACA,mBAC8B;AAC9B,MAAI,EAAE,cAAc,aAAa;AAC/B,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,kBAAkB,YAAY,UAAU;AACvD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,iBAAiB,CACrB,aAC6C;AAC7C,QAAM,YAAY,YAAY,QAAQ,IAAI,oBAAoB,QAAQ,IAAI;AAE1E,SAAO;AAAA,IACL,cAAc,SAAS;AAAA,IACvB,OAAO,QAAQ,CAAC,cAAc;AAC5B,UAAI,CAAC,WAAW;AACd,eAAO,OAAO,QAAQ,IAAI;AAAA,MAC5B;AAEA,YAAM,aAAa,KAAK,WAAW,eAAe;AAClD,YAAM,iBAAiB,KAAK,WAAW,oBAAoB;AAE3D,aAAO;AAAA,QACL,OAAO,IAAI;AAAA,UACT,YAAY,aAAyB,UAAU;AAAA,UAC/C,YAAY,aAAyB,cAAc;AAAA,QACrD,CAAC;AAAA,QACD,OAAO;AAAA,UAAI,CAAC,EAAE,YAAY,WAAW,MACnC;AAAA,YACE;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAOA,IAAM,sBAAsB,CAAC,WAC3B;AAAA,EACE,OAAO,aAAa,UAClB,OAAO,kBAAkB,UACzB,OAAO,WACP,OAAO;AACX;;;AD7NF,IAAM,aAAN,cAAyBC,MAAK,YAAY,YAAY,EAGnD;AAAC;AAEJ,IAAM,qBAAqB;AAE3B,IAAM,kBAAkB,CACtB,WACA,OACA,YAEAC,QAAO,WAAW;AAAA,EAChB,KAAK,YAAY;AACf,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK;AAAA,IAChC;AAEA,QAAI,SAAS;AACX,cAAQ,kBAAkB,IAAI;AAAA,IAChC;AAEA,UAAM,kBAAkB,IAAI;AAAA,MAC1B,IAAI,IAAI,SAAS;AAAA,MACjB;AAAA,QACE,aAAa;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,IAAI;AAAA,MACjB,EAAE,MAAM,mBAAmB,SAAS,QAAQ;AAAA,MAC5C,EAAE,cAAc,CAAC,EAAE;AAAA,IACrB;AAEA,UAAM,OAAO,QAAQ,eAA4B;AACjD,WAAO;AAAA,EACT;AAAA,EACA,OAAO,CAAC,MACN,IAAI,WAAW;AAAA,IACb,MAAM;AAAA,IACN,SAAS,aAAa,QAAQ,EAAE,UAAU;AAAA,EAC5C,CAAC;AACL,CAAC;AAEH,IAAM,gBAAgB,CACpB,MACA,gBAC4B;AAC5B,MAAI,EAAE,eAAe,oBAAoB,WAAW,IAAI;AACtD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,EAAE,GAAG,KAAK;AAEzB,MAAI,YAAY,gBAAgB,UAAa,KAAK,gBAAgB,QAAW;AAC3E,WAAO,cAAc,YAAY;AAAA,EACnC;AACA,MACE,YAAY,qBAAqB,UACjC,KAAK,qBAAqB,QAC1B;AACA,WAAO,mBAAmB,YAAY;AAAA,EACxC;AACA,MAAI,YAAY,YAAY,UAAa,KAAK,YAAY,QAAW;AACnE,WAAO,UAAU,YAAY;AAAA,EAC/B;AACA,MAAI,YAAY,QAAQ,UAAa,KAAK,QAAQ,QAAW;AAC3D,WAAO,MAAM,YAAY;AAAA,EAC3B;AACA,MACE,YAAY,2BAA2B,UACvC,KAAK,2BAA2B,QAChC;AACA,WAAO,yBAAyB,YAAY;AAAA,EAC9C;AACA,MACE,YAAY,0BAA0B,UACtC,KAAK,0BAA0B,QAC/B;AACA,WAAO,wBAAwB,YAAY;AAAA,EAC7C;AAEA,SAAO;AACT;AAEA,IAAM,kBAAkB,CACtB,QACA,gBAEAA,QAAO,WAAW;AAAA,EAChB,KAAK,YAAY;AACf,UAAM,kBAAkB,IAAI,qBAAqB;AAEjD,UAAM,SAAS,IAAI;AAAA,MACjB,EAAE,MAAM,SAAS,SAAS,QAAQ;AAAA,MAClC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,IAChC;AAEA,WAAO,kBAAkB,wBAAwB,YAAY;AAC3D,YAAM,SAAS,MAAM,OAAO,UAAU;AACtC,aAAO;AAAA,IACT,CAAC;AAED,WAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,YAAM,eAAe;AAAA,QACnB,QAAQ,OAAO,aAAa,CAAC;AAAA,QAC7B;AAAA,MACF;AACA,YAAM,SAAS,MAAM,OAAO,SAAS;AAAA,QACnC,MAAM,QAAQ,OAAO;AAAA,QACrB,WAAW;AAAA,MACb,CAAC;AACD,aAAO;AAAA,IACT,CAAC;AAED,UAAM,OAAO,QAAQ,eAAe;AACpC,WAAO;AAAA,EACT;AAAA,EACA,OAAO,CAAC,MACN,IAAI,WAAW;AAAA,IACb,MAAM;AAAA,IACN,SAAS,aAAa,QAAQ,EAAE,UAAU;AAAA,EAC5C,CAAC;AACL,CAAC;AAEH,IAAM,oBAAoB,CACxB,WAIG;AACH,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,UAAU,OAAO,aAAa;AAEpC,SAAOC;AAAA,IACL,gBAAgB,WAAW,OAAO,OAAO,OAAO;AAAA,IAChDD,QAAO;AAAA,MAAQ,CAAC,WACdC;AAAA,QACE,gBAAgB,QAAQ,OAAO,WAAW;AAAA,QAC1CD,QAAO,IAAI,CAAC,WAAW;AACrB,gBAAM,QAAQ,YAAY;AACxB,kBAAM,OAAO,MAAM;AACnB,kBAAM,OAAO,MAAM;AAAA,UACrB;AACA,iBAAO,EAAE,QAAQ,QAAQ,MAAM;AAAA,QACjC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,mBAAmB,CACvB,eACkC;AAClC,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,UAA6B,CAAC;AAEpC,MAAI,WAAW,gBAAgB,QAAW;AACxC,YAAQ,cAAc,WAAW;AAAA,EACnC;AACA,MAAI,WAAW,qBAAqB,QAAW;AAC7C,YAAQ,mBAAmB,WAAW;AAAA,EACxC;AACA,MAAI,WAAW,YAAY,QAAW;AACpC,YAAQ,UAAU,WAAW;AAAA,EAC/B;AACA,MAAI,WAAW,QAAQ,QAAW;AAChC,YAAQ,MAAM,WAAW;AAAA,EAC3B;AACA,MAAI,WAAW,2BAA2B,QAAW;AACnD,YAAQ,yBAAyB,WAAW;AAAA,EAC9C;AACA,MAAI,WAAW,0BAA0B,QAAW;AAClD,YAAQ,wBAAwB,WAAW;AAAA,EAC7C;AACA,MAAI,WAAW,YAAY,QAAW;AACpC,YAAQ,UAAU,WAAW;AAAA,EAC/B;AAEA,SAAO,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,UAAU;AACrD;AAEA,IAAM,iBAAiB,CACrB,eAKS;AACT,MAAI,YAAY,YAAY;AAC1B,IAAAE,SAAQ,OAAO,MAAM,sBAAsB,WAAW,UAAU;AAAA,CAAI;AAAA,EACtE;AACA,MAAI,YAAY,gBAAgB;AAC9B,IAAAA,SAAQ,OAAO;AAAA,MACb,2BAA2B,WAAW,cAAc;AAAA;AAAA,IACtD;AAAA,EACF;AACF;AAEA,IAAM,gBAAgB,MACpBD;AAAA,EACE,eAAe;AAAA,EACfD,QAAO,QAAQ,CAAC,eAAe;AAC7B,UAAM,QAAQ,YAAY,SAASE,SAAQ,IAAI;AAC/C,QAAI,CAAC,OAAO;AACV,aAAOF,QAAO;AAAA,QACZ,IAAI,WAAW;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,YAAY,YAAY,aAAaE,SAAQ,IAAI;AACvD,mBAAe,UAAU;AAEzB,WAAOF,QAAO,QAAQ;AAAA,MACpB;AAAA,MACA;AAAA,MACA,aAAa,iBAAiB,UAAU;AAAA,IAC1C,CAAC;AAAA,EACH,CAAC;AACH;AAEF,IAAM,aAAa,MACjBC;AAAA,EACE,cAAc;AAAA,EACdD,QAAO,QAAQ,iBAAiB;AAAA,EAChCA,QAAO;AAAA,IAAQ,CAAC,EAAE,MAAM,MACtBA,QAAO,MAAwB,MAAM;AACnC,YAAM,WAAW,YAAY;AAC3B,cAAM,MAAM;AACZ,QAAAE,SAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,MAAAA,SAAQ,GAAG,UAAU,QAAQ;AAC7B,MAAAA,SAAQ,GAAG,WAAW,QAAQ;AAC9B,MAAAA,SAAQ,MAAM,OAAO;AAAA,IACvB,CAAC;AAAA,EACH;AACF;AAEF,IAAM,oBAAoB,YAA2B;AACnD,QAAMF,QAAO;AAAA,IACXC;AAAA,MACE,WAAW;AAAA,MACXD,QAAO,SAAS,CAAC,UAAgC;AAC/C,QAAAE,SAAQ,OAAO,MAAM,yBAAyB,MAAM,OAAO;AAAA,CAAI;AAC/D,eAAOA,SAAQ,KAAK,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":["process","Data","Effect","pipe","Data","Effect","pipe","process"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@braid-cloud/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "braid MCP stdio proxy - forwards MCP requests to the hosted braid server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"braid-mcp": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/proxy.js",
|
|
10
|
+
"types": "./dist/proxy.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/proxy.d.ts",
|
|
14
|
+
"import": "./dist/proxy.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"dev:run": "tsx src/cli.ts",
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"lint": "biome check src",
|
|
26
|
+
"lint:fix": "biome check --write src",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
32
|
+
"effect": "^3.19.14"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@braid-cloud/config": "workspace:*",
|
|
36
|
+
"@types/node": "^25.0.10",
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
|
+
"tsx": "^4.21.0",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"mcp",
|
|
43
|
+
"model-context-protocol",
|
|
44
|
+
"braid",
|
|
45
|
+
"prompts",
|
|
46
|
+
"rules",
|
|
47
|
+
"ai"
|
|
48
|
+
],
|
|
49
|
+
"author": "braid <hello@braid.cloud>",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"homepage": "https://braid.cloud",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18"
|
|
54
|
+
}
|
|
55
|
+
}
|