github-mcp-sketch 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/LICENSE +21 -0
- package/README.md +239 -0
- package/dist/cache.js +34 -0
- package/dist/cache.js.map +1 -0
- package/dist/const/messages.js +20 -0
- package/dist/const/messages.js.map +1 -0
- package/dist/const/tools.js +40 -0
- package/dist/const/tools.js.map +1 -0
- package/dist/metrics.js +45 -0
- package/dist/metrics.js.map +1 -0
- package/dist/server.js +133 -0
- package/dist/server.js.map +1 -0
- package/dist/sketch.js +9 -0
- package/dist/sketch.js.map +1 -0
- package/dist/upstream.js +32 -0
- package/dist/upstream.js.map +1 -0
- package/dist/util/errors.js +7 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/parse.js +19 -0
- package/dist/util/parse.js.map +1 -0
- package/dist/util/shutdown.js +29 -0
- package/dist/util/shutdown.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Adel Nawar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# github-mcp-sketch
|
|
2
|
+
|
|
3
|
+
A local MCP server that proxies the [GitHub MCP](https://github.com/github/github-mcp-server) and returns the JSON's inferred schema instead of the raw response. Adds a `query_response` tool the agent uses to pull specific fields it actually needs.
|
|
4
|
+
|
|
5
|
+
The point: most GitHub API responses are 80%+ metadata the agent doesn't read. `list_pull_requests` for 30 PRs is ~80KB; the agent usually needs 4–5 fields per PR. Returning a schema first lets the model see the shape and decide what to fetch back.
|
|
6
|
+
|
|
7
|
+
## What the numbers look like
|
|
8
|
+
|
|
9
|
+
Benchmarked across 13 agentic tasks on [`facebook/react`](https://github.com/facebook/react) and [`kubernetes/kubernetes`](https://github.com/kubernetes/kubernetes), Anthropic SDK with prompt caching enabled (matching how Claude Code uses the API). N=3 runs per case per side. Sum of medians across all 13 cases:
|
|
10
|
+
|
|
11
|
+
| Metric | Baseline (`github`) | Sketch (`github-sketch`) | Δ |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| Final context size (tokens) | 844,136 | 348,292 | **−58.7%** |
|
|
14
|
+
| API cost (Claude Opus 4.7) | $5.59 | $2.73 | **−51.2%** |
|
|
15
|
+
| Total tokens billed | 1.84M | 1.27M | −30.8% |
|
|
16
|
+
|
|
17
|
+
Where the win lives — the 13 cases grouped by the shape of response they exercise:
|
|
18
|
+
|
|
19
|
+
| Response shape | Cases | Δ context | Δ cost |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| Single-object fetches (one issue, one PR) | 2 | +3% | +52% |
|
|
22
|
+
| List endpoints with rich metadata (30+ items, lots of URL/reaction noise) | 3 | **−74%** | **−71%** |
|
|
23
|
+
| Comment-heavy threads (KEP review, long React discussion) | 3 | **−69%** | **−57%** |
|
|
24
|
+
| Multi-step agentic workflows (triage, investigation, drill-down) | 3 | **−40%** | **−27%** |
|
|
25
|
+
| File contents and commit history | 2 | −2% | 0% |
|
|
26
|
+
|
|
27
|
+
The first and last rows are where the proxy doesn't help. On a single issue with ~15 fields, the agent queries back ~11 of them anyway — no noise to skip, and the extra round-trip costs more than it saves. On raw file source, the contents are opaque text — schemas can't compress text. Both were included in the benchmark intentionally so the data isn't cherry-picked.
|
|
28
|
+
|
|
29
|
+
Full report and reproducible bench: [`json-schema-sketch-bench`](https://github.com/markadelnawar/json-schema-sketch-bench).
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
Every upstream tool call (`list_issues`, `pull_request_read`, etc.) goes through this pipeline:
|
|
34
|
+
|
|
35
|
+
1. The proxy forwards the call to `https://api.githubcopilot.com/mcp` with the agent-supplied PAT.
|
|
36
|
+
2. The raw JSON response is cached in memory under a generated `cache_id`.
|
|
37
|
+
3. The response shape is inferred via [`json-schema-sketch`](https://github.com/markadelnawar/json-schema-sketch) into a compact text-form schema.
|
|
38
|
+
4. The proxy returns `cache_id: gh-1-abc (pass to query_response)` followed by the schema. That's the entire tool result the agent sees.
|
|
39
|
+
|
|
40
|
+
When the agent wants actual values, it calls `query_response`:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
query_response(cache_id="gh-1-abc", path="items[*].title")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Path syntax supports dot/bracket navigation and `[*]` wildcards. The agent can pass an array of paths to batch multiple field extractions into one tool call:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
query_response(cache_id="gh-1-abc", path=["items[*].title", "items[*].state", "items[*].user.login"])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Cache entries live until the proxy process exits. There's no TTL; the design assumes one agent session per proxy process.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
Requires Node 20+.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g github-mcp-sketch
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You'll need a GitHub Personal Access Token (fine-grained, read-only is enough). [Create one](https://github.com/settings/personal-access-tokens/new) with content and metadata read on whatever repos you'll use it against.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
All configuration is via environment variables.
|
|
67
|
+
|
|
68
|
+
| Variable | Required | Default | Purpose |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `GITHUB_PAT` | yes | — | Bearer token forwarded as the `Authorization` header on every upstream request. |
|
|
71
|
+
| `UPSTREAM_MCP_URL` | no | `https://api.githubcopilot.com/mcp` | Upstream MCP endpoint. Override for testing or self-hosted GitHub MCPs. |
|
|
72
|
+
| `CACHE_SIZE` | no | `50` | Max number of cached responses kept in memory before LRU eviction. Each cached entry is one upstream tool result. |
|
|
73
|
+
| `METRICS_CSV` | no | *(off)* | If set to a file path, the proxy appends per-tool-call metrics (raw vs sketched response size, tokens, timing) to that CSV. Off by default — set it explicitly to opt in. |
|
|
74
|
+
|
|
75
|
+
How the variables get set depends on how you launch the proxy:
|
|
76
|
+
|
|
77
|
+
- **From an MCP host** (Claude Code, Claude Desktop, Cline, etc.) — the host passes env vars to the spawned process. In Claude Code that's the `-e KEY=VALUE` flag of `claude mcp add` (shown below). Values are stored in the host's MCP config (`~/.claude.json` for Claude Code).
|
|
78
|
+
- **Direct shell invocation** — `GITHUB_PAT=ghp_... github-mcp-sketch`, or export the variable in your shell rc.
|
|
79
|
+
- **Running from a clone of the source** — copy `.env.example` → `.env`, fill it in. Dotenv loads it on startup. (`.env` is gitignored.)
|
|
80
|
+
|
|
81
|
+
## Wire into Claude Code
|
|
82
|
+
|
|
83
|
+
Minimal — just the required PAT:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
claude mcp add github-sketch \
|
|
87
|
+
-e GITHUB_PAT=<your_pat> \
|
|
88
|
+
-- npx github-mcp-sketch
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
With optional variables — stack additional `-e KEY=VALUE` flags before the `--`:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
claude mcp add github-sketch \
|
|
95
|
+
-e GITHUB_PAT=<your_pat> \
|
|
96
|
+
-e CACHE_SIZE=200 \
|
|
97
|
+
-e METRICS_CSV=/tmp/github-sketch-metrics.csv \
|
|
98
|
+
-- npx github-mcp-sketch
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
All `-e` values are persisted in `~/.claude.json` and passed to the proxy on every spawn.
|
|
102
|
+
|
|
103
|
+
Verify the server is registered:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
claude mcp list | grep github-sketch
|
|
107
|
+
# github-sketch: npx github-mcp-sketch - ✓ Connected
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Restart Claude Code so the new server is registered.
|
|
111
|
+
|
|
112
|
+
### Changing variables after wire-up
|
|
113
|
+
|
|
114
|
+
Claude Code doesn't expose an in-place edit for MCP env vars. To change one:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
claude mcp remove github-sketch
|
|
118
|
+
claude mcp add github-sketch -e GITHUB_PAT=<new_pat> -- npx github-mcp-sketch
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or edit `~/.claude.json` directly under the `mcpServers.github-sketch.env` block.
|
|
122
|
+
|
|
123
|
+
## Wire into other MCP hosts
|
|
124
|
+
|
|
125
|
+
The proxy is a standard stdio MCP — anything that speaks MCP can host it. The general pattern:
|
|
126
|
+
|
|
127
|
+
- Spawn `npx github-mcp-sketch` as a stdio process
|
|
128
|
+
- Pass env vars (at minimum `GITHUB_PAT`) on that spawn
|
|
129
|
+
|
|
130
|
+
For example, in a custom Anthropic SDK app using `@modelcontextprotocol/sdk`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
134
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
135
|
+
|
|
136
|
+
const transport = new StdioClientTransport({
|
|
137
|
+
command: "npx",
|
|
138
|
+
args: ["github-mcp-sketch"],
|
|
139
|
+
env: {
|
|
140
|
+
...process.env,
|
|
141
|
+
GITHUB_PAT: process.env.GITHUB_PAT!,
|
|
142
|
+
CACHE_SIZE: "100", // optional
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const client = new Client({ name: "my-app", version: "0.1.0" });
|
|
146
|
+
await client.connect(transport);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Verify:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
claude mcp list | grep github-sketch
|
|
153
|
+
# github-sketch: node /.../dist/server.js - ✓ Connected
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Restart Claude Code so the new server is registered. The proxy exposes the same tool names as the upstream GitHub MCP (`list_issues`, `pull_request_read`, `get_file_contents`, etc.) plus the added `query_response`.
|
|
157
|
+
|
|
158
|
+
## Tools
|
|
159
|
+
|
|
160
|
+
The proxy passes through every tool from the upstream GitHub MCP with identical names, descriptions, and input schemas. The agent calls them exactly as it would call the upstream MCP.
|
|
161
|
+
|
|
162
|
+
The one added tool:
|
|
163
|
+
|
|
164
|
+
### `query_response(cache_id, path, max_items?)`
|
|
165
|
+
|
|
166
|
+
Extract values from a response cached by a previous tool call.
|
|
167
|
+
|
|
168
|
+
**Path syntax:**
|
|
169
|
+
|
|
170
|
+
| Path | Returns |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `""` (empty string) | The whole cached value |
|
|
173
|
+
| `"user.login"` | A nested scalar |
|
|
174
|
+
| `"[0].title"` | First array item's field |
|
|
175
|
+
| `"[*].title"` | A field from every array item |
|
|
176
|
+
| `"items[*].user.login"` | Nested field across an array |
|
|
177
|
+
|
|
178
|
+
**Batch mode** — pass an array of paths to get multiple fields in one call:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
path=["items[*].number", "items[*].title", "items[*].state"]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Returns `{ results: { "items[*].number": [...], "items[*].title": [...], ... } }`. Per-path errors are reported in that path's entry; other paths still succeed.
|
|
185
|
+
|
|
186
|
+
**`max_items`** — optional cap on items returned per wildcard expansion. Defaults to unlimited; the agent only sets it when it explicitly wants a sample.
|
|
187
|
+
|
|
188
|
+
## When it doesn't help
|
|
189
|
+
|
|
190
|
+
Looking at the per-tier table above:
|
|
191
|
+
|
|
192
|
+
- **Single-object fetches of small objects** (one issue, one user) — the agent queries back most of the fields anyway, so the schema + query overhead exceeds the per-call wire savings.
|
|
193
|
+
- **File contents** — raw file text isn't structured, so there's no schema noise to skip. The proxy passes the file through with minor envelope savings only.
|
|
194
|
+
- **Tasks where the agent really does need every field** — almost never happens for list and search endpoints, but is the failure mode if it does.
|
|
195
|
+
|
|
196
|
+
Known variance: on `t3-06` (a 67-comment review thread on a Kubernetes KEP), one of three sketch runs occasionally consumes ~60K context (vs ~39K median) because the agent issues a doubly-nested wildcard query (`review_threads[*].comments[*].body`) that materializes most of the original payload. The median is still ~56% better than baseline, but this is a real pattern the agent can fall into. The proxy doesn't currently guard against it.
|
|
197
|
+
|
|
198
|
+
## Reproduce the bench
|
|
199
|
+
|
|
200
|
+
The benchmark is a separate repo: [`json-schema-sketch-bench`](https://github.com/markadelnawar/json-schema-sketch-bench).
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
git clone https://github.com/markadelnawar/json-schema-sketch-bench
|
|
204
|
+
cd json-schema-sketch-bench
|
|
205
|
+
npm install
|
|
206
|
+
cp .env.example .env # add ANTHROPIC_API_KEY and GITHUB_PAT
|
|
207
|
+
npm run bench # 13 cases × 2 sides × 3 runs, ~45 min, ~$5-10
|
|
208
|
+
npm run report # generates summary.md
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Cases, raw CSVs, methodology details, and the final `summary.md` from a recent run are all checked in.
|
|
212
|
+
|
|
213
|
+
## Architecture
|
|
214
|
+
|
|
215
|
+
The proxy is a thin stdio MCP server:
|
|
216
|
+
|
|
217
|
+
- Connects to `https://api.githubcopilot.com/mcp` over Streamable HTTP using the agent-provided `GITHUB_PAT`.
|
|
218
|
+
- On startup, calls upstream `listTools()` and registers each tool with its original name and description. Adds `query_response` to the manifest.
|
|
219
|
+
- On `tools/call`: if the name is `query_response`, resolves the path against the cache; otherwise forwards upstream, caches the parsed JSON, infers the schema, and returns the wrapped response.
|
|
220
|
+
|
|
221
|
+
There's no rate limiting, no retry, no auth refresh. Upstream errors are returned verbatim.
|
|
222
|
+
|
|
223
|
+
## Caveats
|
|
224
|
+
|
|
225
|
+
- **Stateful in memory.** Cache entries live until the proxy process exits. Restart Claude Code = empty cache.
|
|
226
|
+
- **One PAT per process.** The proxy reads `GITHUB_PAT` once at startup. To rotate, restart.
|
|
227
|
+
- **Schemas drop list-element variance.** If items in an array have different shapes, the schema picks a representative one. Rarely a problem for GitHub responses but can be surprising.
|
|
228
|
+
- **No write-tool optimization.** Tools that perform writes (`create_issue`, `merge_pull_request`) work but the response sketching is wasted on small confirmation payloads.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT. See [LICENSE](LICENSE).
|
|
233
|
+
|
|
234
|
+
## Related
|
|
235
|
+
|
|
236
|
+
- [`json-schema-sketch`](https://github.com/markadelnawar/json-schema-sketch) — the underlying library that infers compact text-form schemas from JSON values.
|
|
237
|
+
- [`json-schema-sketch-bench`](https://github.com/markadelnawar/json-schema-sketch-bench) — the benchmark harness used to produce the numbers above.
|
|
238
|
+
- [GitHub MCP server](https://github.com/github/github-mcp-server) — the upstream this proxies.
|
|
239
|
+
- [Model Context Protocol](https://modelcontextprotocol.io) — the protocol both speak.
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class ResponseCache {
|
|
2
|
+
maxSize;
|
|
3
|
+
store = new Map();
|
|
4
|
+
counter = 0;
|
|
5
|
+
constructor(maxSize) {
|
|
6
|
+
this.maxSize = maxSize;
|
|
7
|
+
}
|
|
8
|
+
put(tool, args, response) {
|
|
9
|
+
this.counter += 1;
|
|
10
|
+
const id = `gh-${this.counter}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11
|
+
this.store.set(id, { tool, args, response, createdAt: Date.now() });
|
|
12
|
+
while (this.store.size > this.maxSize) {
|
|
13
|
+
const oldest = this.store.keys().next().value;
|
|
14
|
+
if (oldest === undefined)
|
|
15
|
+
break;
|
|
16
|
+
this.store.delete(oldest);
|
|
17
|
+
}
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
get(id) {
|
|
21
|
+
return this.store.get(id);
|
|
22
|
+
}
|
|
23
|
+
size() {
|
|
24
|
+
return this.store.size;
|
|
25
|
+
}
|
|
26
|
+
list() {
|
|
27
|
+
return Array.from(this.store.entries()).map(([id, e]) => ({
|
|
28
|
+
id,
|
|
29
|
+
tool: e.tool,
|
|
30
|
+
createdAt: e.createdAt,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,aAAa;IAIJ;IAHZ,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IACtC,OAAO,GAAG,CAAC,CAAC;IAEpB,YAAoB,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;IAEvC,GAAG,CAAC,IAAY,EAAE,IAAa,EAAE,QAAiB;QAChD,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC9C,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM;YAChC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,IAAI;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxD,EAAE;YACF,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const MSG_MISSING_PAT = "[github-mcp-sketch] GITHUB_PAT is not set. Refusing to start.";
|
|
2
|
+
export const MSG_READY = "[github-mcp-sketch] ready on stdio.";
|
|
3
|
+
export function msgUpstreamConnected(toolCount) {
|
|
4
|
+
return `[github-mcp-sketch] Connected upstream. ${toolCount} tools available.`;
|
|
5
|
+
}
|
|
6
|
+
export function msgUnknownCacheId(cacheId, available) {
|
|
7
|
+
const list = available.length === 0 ? "(none)" : available.join(", ");
|
|
8
|
+
return (`Unknown cache_id "${cacheId}". Available: ${list}. ` +
|
|
9
|
+
`Call an upstream tool first to populate the cache.`);
|
|
10
|
+
}
|
|
11
|
+
export function wrapSketchResponse(cacheId, schema) {
|
|
12
|
+
return `cache_id: ${cacheId} (pass to query_response)\n\n${schema}`;
|
|
13
|
+
}
|
|
14
|
+
export function msgPathCommaHint(path) {
|
|
15
|
+
return (`Path "${path}" contains a comma. Path strings do NOT support comma-separated field selection. ` +
|
|
16
|
+
`To get multiple fields in one call, pass an array of paths instead: ` +
|
|
17
|
+
`path=["[*].title","[*].state","[*].user.login"]. Each path is run against the same cached response ` +
|
|
18
|
+
`and the results are returned together in one tool call.`);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/const/messages.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAC1B,+DAA+D,CAAC;AAElE,MAAM,CAAC,MAAM,SAAS,GAAG,qCAAqC,CAAC;AAE/D,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO,2CAA2C,SAAS,mBAAmB,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,SAAmB;IACpE,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CACL,qBAAqB,OAAO,iBAAiB,IAAI,IAAI;QACrD,oDAAoD,CACrD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,MAAc;IAChE,OAAO,aAAa,OAAO,gCAAgC,MAAM,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,CACL,SAAS,IAAI,mFAAmF;QAChG,sEAAsE;QACtE,qGAAqG;QACrG,yDAAyD,CAC1D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const QUERY_RESPONSE_TOOL = {
|
|
2
|
+
name: "query_response",
|
|
3
|
+
description: "Extract values from a response cached by a previous tool call.\n\n" +
|
|
4
|
+
"PATH SYNTAX (per path):\n" +
|
|
5
|
+
' "user.login" → nested key\n' +
|
|
6
|
+
' "[0].title" → array index, then key\n' +
|
|
7
|
+
' "[*].title" → wildcard: title from each array element\n' +
|
|
8
|
+
' "items[*].user.login" → wildcard with nested key\n' +
|
|
9
|
+
' "" → return the whole cached value\n\n' +
|
|
10
|
+
"BATCH MULTIPLE FIELDS IN ONE CALL — pass `path` as an array of strings:\n" +
|
|
11
|
+
' path: ["[*].title", "[*].state", "[*].user.login"]\n' +
|
|
12
|
+
" → returns { results: { \"<path>\": {value,...}, ... } }, one entry per path.\n" +
|
|
13
|
+
" Per-path errors are reported in that path's entry; other paths still succeed.\n\n" +
|
|
14
|
+
"Notes:\n" +
|
|
15
|
+
" - Do NOT comma-separate fields inside a single path string. Use the array form.\n" +
|
|
16
|
+
" - max_items applies to every wildcard expansion in the batch.\n" +
|
|
17
|
+
" - Single string returns the value directly; array returns a results map.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
cache_id: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "cache_id returned by a previous proxy tool call.",
|
|
24
|
+
},
|
|
25
|
+
path: {
|
|
26
|
+
oneOf: [
|
|
27
|
+
{ type: "string" },
|
|
28
|
+
{ type: "array", items: { type: "string" }, minItems: 1 },
|
|
29
|
+
],
|
|
30
|
+
description: 'Single path string OR array of path strings. Examples: "user.login" | "[*].title" | ["[*].title", "[*].state"]. Empty string returns the whole cached value. No commas inside a path.',
|
|
31
|
+
},
|
|
32
|
+
max_items: {
|
|
33
|
+
type: "number",
|
|
34
|
+
description: "OPTIONAL cap on items returned per wildcard expansion. Default unlimited — returns all items. Set this only when you specifically want a sample (e.g. \"give me the first 5 titles to scan\"). Applies to every path in a batch.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["cache_id", "path"],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/const/tools.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAS;IACvC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,oEAAoE;QACpE,2BAA2B;QAC3B,2CAA2C;QAC3C,sDAAsD;QACtD,wEAAwE;QACxE,yDAAyD;QACzD,gEAAgE;QAChE,2EAA2E;QAC3E,wDAAwD;QACxD,kFAAkF;QAClF,qFAAqF;QACrF,UAAU;QACV,qFAAqF;QACrF,mEAAmE;QACnE,4EAA4E;IAC9E,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,QAAQ,EAAE;gBACR,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,kDAAkD;aAChE;YACD,IAAI,EAAE;gBACJ,KAAK,EAAE;oBACL,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAClB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;iBAC1D;gBACD,WAAW,EACT,uLAAuL;aAC1L;YACD,SAAS,EAAE;gBACT,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,kOAAkO;aACrO;SACF;QACD,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC;KAC/B;CACF,CAAC"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { get_encoding } from "tiktoken";
|
|
3
|
+
const HEADER = "timestamp,tool,cache_id,raw_bytes,sketched_bytes,bytes_saved_pct,raw_tokens,sketched_tokens,tokens_saved_pct\n";
|
|
4
|
+
let encoder = null;
|
|
5
|
+
function getEncoder() {
|
|
6
|
+
if (!encoder)
|
|
7
|
+
encoder = get_encoding("cl100k_base");
|
|
8
|
+
return encoder;
|
|
9
|
+
}
|
|
10
|
+
export function freeEncoder() {
|
|
11
|
+
encoder?.free();
|
|
12
|
+
encoder = null;
|
|
13
|
+
}
|
|
14
|
+
function countTokens(text) {
|
|
15
|
+
return getEncoder().encode(text).length;
|
|
16
|
+
}
|
|
17
|
+
function byteLen(text) {
|
|
18
|
+
return Buffer.byteLength(text, "utf8");
|
|
19
|
+
}
|
|
20
|
+
function pct(saved, raw) {
|
|
21
|
+
if (raw <= 0)
|
|
22
|
+
return 0;
|
|
23
|
+
return Math.round((saved / raw) * 1000) / 10;
|
|
24
|
+
}
|
|
25
|
+
export function recordMetric(csvPath, row) {
|
|
26
|
+
if (!existsSync(csvPath))
|
|
27
|
+
writeFileSync(csvPath, HEADER);
|
|
28
|
+
const rawBytes = byteLen(row.rawText);
|
|
29
|
+
const sketchedBytes = byteLen(row.sketchedText);
|
|
30
|
+
const rawTokens = countTokens(row.rawText);
|
|
31
|
+
const sketchedTokens = countTokens(row.sketchedText);
|
|
32
|
+
const line = [
|
|
33
|
+
new Date().toISOString(),
|
|
34
|
+
row.tool,
|
|
35
|
+
row.cacheId,
|
|
36
|
+
rawBytes,
|
|
37
|
+
sketchedBytes,
|
|
38
|
+
pct(rawBytes - sketchedBytes, rawBytes),
|
|
39
|
+
rawTokens,
|
|
40
|
+
sketchedTokens,
|
|
41
|
+
pct(rawTokens - sketchedTokens, rawTokens),
|
|
42
|
+
].join(",");
|
|
43
|
+
appendFileSync(csvPath, line + "\n");
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,YAAY,EAAiB,MAAM,UAAU,CAAC;AAEvD,MAAM,MAAM,GACV,gHAAgH,CAAC;AAEnH,IAAI,OAAO,GAAoB,IAAI,CAAC;AAEpC,SAAS,UAAU;IACjB,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,IAAI,EAAE,CAAC;IAChB,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,UAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,GAAG,CAAC,KAAa,EAAE,GAAW;IACrC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAC/C,CAAC;AASD,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,GAAc;IAC1D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAEzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG;QACX,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACxB,GAAG,CAAC,IAAI;QACR,GAAG,CAAC,OAAO;QACX,QAAQ;QACR,aAAa;QACb,GAAG,CAAC,QAAQ,GAAG,aAAa,EAAE,QAAQ,CAAC;QACvC,SAAS;QACT,cAAc;QACd,GAAG,CAAC,SAAS,GAAG,cAAc,EAAE,SAAS,CAAC;KAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEZ,cAAc,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { config as loadEnv } from "dotenv";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { ResponseCache } from "./cache.js";
|
|
7
|
+
import { sketchValue, queryValue } from "./sketch.js";
|
|
8
|
+
import { recordMetric, freeEncoder } from "./metrics.js";
|
|
9
|
+
import { UpstreamMcp } from "./upstream.js";
|
|
10
|
+
import { QUERY_RESPONSE_TOOL } from "./const/tools.js";
|
|
11
|
+
import { MSG_MISSING_PAT, MSG_READY, msgUpstreamConnected, msgUnknownCacheId, msgPathCommaHint, wrapSketchResponse, } from "./const/messages.js";
|
|
12
|
+
import { extractTextFromUpstream, tryParseJson } from "./util/parse.js";
|
|
13
|
+
import { buildToolError } from "./util/errors.js";
|
|
14
|
+
import { registerShutdown } from "./util/shutdown.js";
|
|
15
|
+
loadEnv();
|
|
16
|
+
const PAT = process.env.GITHUB_PAT;
|
|
17
|
+
const UPSTREAM_URL = process.env.UPSTREAM_MCP_URL ?? "https://api.githubcopilot.com/mcp";
|
|
18
|
+
const CACHE_SIZE = Number(process.env.CACHE_SIZE ?? 50);
|
|
19
|
+
const METRICS_CSV = process.env.METRICS_CSV ?? null;
|
|
20
|
+
if (!PAT) {
|
|
21
|
+
console.error(MSG_MISSING_PAT);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
async function main() {
|
|
25
|
+
const upstream = new UpstreamMcp(UPSTREAM_URL, PAT);
|
|
26
|
+
await upstream.connect();
|
|
27
|
+
const upstreamTools = await upstream.listTools();
|
|
28
|
+
console.error(msgUpstreamConnected(upstreamTools.length));
|
|
29
|
+
const cache = new ResponseCache(CACHE_SIZE);
|
|
30
|
+
const server = new Server({ name: "github-mcp-sketch", version: "0.0.1" }, { capabilities: { tools: {} } });
|
|
31
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
32
|
+
// Pass upstream tool descriptions through unchanged. The schema-sketch
|
|
33
|
+
// behavior teaches itself on the first call: every response wraps with
|
|
34
|
+
// `cache_id: gh-N-xxx (pass to query_response)\n\n<schema>`, and the
|
|
35
|
+
// `query_response` tool's own description explains how to use it.
|
|
36
|
+
// Appending a per-tool NOTE here would cost ~60 tokens × every upstream
|
|
37
|
+
// tool, which on heavy tool sets is several thousand wasted input tokens
|
|
38
|
+
// per request — the very thing the proxy is supposed to save.
|
|
39
|
+
const tools = upstreamTools.map((t) => ({ ...t }));
|
|
40
|
+
tools.push(QUERY_RESPONSE_TOOL);
|
|
41
|
+
return { tools };
|
|
42
|
+
});
|
|
43
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
44
|
+
const toolName = req.params.name;
|
|
45
|
+
const args = req.params.arguments;
|
|
46
|
+
if (toolName === "query_response") {
|
|
47
|
+
const argsObj = (args ?? {});
|
|
48
|
+
const cacheId = String(argsObj.cache_id ?? "");
|
|
49
|
+
const maxItems = typeof argsObj.max_items === "number" ? argsObj.max_items : Infinity;
|
|
50
|
+
const entry = cache.get(cacheId);
|
|
51
|
+
if (!entry) {
|
|
52
|
+
return buildToolError(msgUnknownCacheId(cacheId, cache.list().map((e) => e.id)));
|
|
53
|
+
}
|
|
54
|
+
const pathInput = argsObj.path;
|
|
55
|
+
const isBatch = Array.isArray(pathInput);
|
|
56
|
+
const paths = isBatch
|
|
57
|
+
? pathInput.map((p) => String(p))
|
|
58
|
+
: [String(pathInput ?? "")];
|
|
59
|
+
const resolveOne = (p) => {
|
|
60
|
+
const r = queryValue(entry.response, p, maxItems);
|
|
61
|
+
if (!r.ok) {
|
|
62
|
+
const msg = p.includes(",")
|
|
63
|
+
? `${msgPathCommaHint(p)}\n\nOriginal parser error: ${r.error}`
|
|
64
|
+
: r.error;
|
|
65
|
+
return { error: msg };
|
|
66
|
+
}
|
|
67
|
+
const out = { value: r.value };
|
|
68
|
+
const returned = Array.isArray(r.value) ? r.value.length : 0;
|
|
69
|
+
if (r.totalItems !== undefined && r.totalItems > returned) {
|
|
70
|
+
out.totalItems = r.totalItems;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
if (!isBatch) {
|
|
75
|
+
const single = resolveOne(paths[0]);
|
|
76
|
+
if ("error" in single)
|
|
77
|
+
return buildToolError(single.error);
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: JSON.stringify(single, null, 2) }],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const results = {};
|
|
83
|
+
for (const p of paths)
|
|
84
|
+
results[p] = resolveOne(p);
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: JSON.stringify({ results }, null, 2) }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const upstreamResult = await upstream.callTool(toolName, args);
|
|
90
|
+
if (upstreamResult.isError)
|
|
91
|
+
return upstreamResult;
|
|
92
|
+
const rawText = extractTextFromUpstream(upstreamResult);
|
|
93
|
+
if (rawText === null)
|
|
94
|
+
return upstreamResult;
|
|
95
|
+
const parsed = tryParseJson(rawText);
|
|
96
|
+
if (parsed === undefined)
|
|
97
|
+
return upstreamResult;
|
|
98
|
+
const schema = sketchValue(parsed);
|
|
99
|
+
const cacheId = cache.put(toolName, args, parsed);
|
|
100
|
+
const wrapped = wrapSketchResponse(cacheId, schema);
|
|
101
|
+
if (METRICS_CSV) {
|
|
102
|
+
try {
|
|
103
|
+
recordMetric(METRICS_CSV, {
|
|
104
|
+
tool: toolName,
|
|
105
|
+
cacheId,
|
|
106
|
+
rawText,
|
|
107
|
+
sketchedText: wrapped,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error("[github-mcp-sketch] metrics write failed:", err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { content: [{ type: "text", text: wrapped }] };
|
|
115
|
+
});
|
|
116
|
+
registerShutdown([
|
|
117
|
+
async () => {
|
|
118
|
+
try {
|
|
119
|
+
await upstream.close();
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
},
|
|
123
|
+
() => freeEncoder(),
|
|
124
|
+
]);
|
|
125
|
+
const transport = new StdioServerTransport();
|
|
126
|
+
await server.connect(transport);
|
|
127
|
+
console.error(MSG_READY);
|
|
128
|
+
}
|
|
129
|
+
main().catch((err) => {
|
|
130
|
+
console.error("[github-mcp-sketch] fatal:", err);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/sketch.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { inferSchema, renderSchema, resolvePath } from "json-schema-sketch";
|
|
2
|
+
export function sketchValue(value) {
|
|
3
|
+
const node = inferSchema(value);
|
|
4
|
+
return renderSchema(node);
|
|
5
|
+
}
|
|
6
|
+
export function queryValue(value, path, maxItems) {
|
|
7
|
+
return resolvePath(value, path, maxItems);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=sketch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sketch.js","sourceRoot":"","sources":["../src/sketch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAG5E,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAc,EACd,IAAY,EACZ,QAAgB;IAEhB,OAAO,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC"}
|
package/dist/upstream.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
export class UpstreamMcp {
|
|
4
|
+
client;
|
|
5
|
+
transport;
|
|
6
|
+
constructor(url, pat) {
|
|
7
|
+
this.transport = new StreamableHTTPClientTransport(new URL(url), {
|
|
8
|
+
requestInit: {
|
|
9
|
+
headers: { Authorization: `Bearer ${pat}` },
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
this.client = new Client({ name: "github-mcp-sketch", version: "0.0.1" }, { capabilities: {} });
|
|
13
|
+
}
|
|
14
|
+
async connect() {
|
|
15
|
+
await this.client.connect(this.transport);
|
|
16
|
+
}
|
|
17
|
+
async close() {
|
|
18
|
+
await this.client.close();
|
|
19
|
+
}
|
|
20
|
+
async listTools() {
|
|
21
|
+
const result = await this.client.listTools();
|
|
22
|
+
return result.tools;
|
|
23
|
+
}
|
|
24
|
+
async callTool(name, args) {
|
|
25
|
+
const result = await this.client.callTool({
|
|
26
|
+
name,
|
|
27
|
+
arguments: (args ?? {}),
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=upstream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upstream.js","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AASnG,MAAM,OAAO,WAAW;IACd,MAAM,CAAS;IACf,SAAS,CAAgC;IAEjD,YAAY,GAAW,EAAE,GAAW;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,6BAA6B,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE;YAC/D,WAAW,EAAE;gBACX,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,GAAG,EAAE,EAAE;aAC5C;SACF,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CACtB,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,EAC/C,EAAE,YAAY,EAAE,EAAE,EAAE,CACrB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAC7C,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,IAAa;QACxC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YACxC,IAAI;YACJ,SAAS,EAAE,CAAC,IAAI,IAAI,EAAE,CAA4B;SACnD,CAAC,CAAC;QACH,OAAO,MAA4B,CAAC;IACtC,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function extractTextFromUpstream(result) {
|
|
2
|
+
if (!result.content || !Array.isArray(result.content))
|
|
3
|
+
return null;
|
|
4
|
+
const texts = result.content
|
|
5
|
+
.filter((c) => c.type === "text" && typeof c.text === "string")
|
|
6
|
+
.map((c) => c.text);
|
|
7
|
+
if (texts.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
return texts.join("\n");
|
|
10
|
+
}
|
|
11
|
+
export function tryParseJson(text) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/util/parse.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,MAA0B;IAChE,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;SAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function registerShutdown(handlers) {
|
|
2
|
+
let shuttingDown = false;
|
|
3
|
+
const run = async (reason, exitCode) => {
|
|
4
|
+
if (shuttingDown)
|
|
5
|
+
return;
|
|
6
|
+
shuttingDown = true;
|
|
7
|
+
console.error(`[github-mcp-sketch] shutting down (${reason})`);
|
|
8
|
+
for (const h of handlers) {
|
|
9
|
+
try {
|
|
10
|
+
await h();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
console.error("[github-mcp-sketch] shutdown handler error:", err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
process.exit(exitCode);
|
|
17
|
+
};
|
|
18
|
+
process.on("SIGINT", () => void run("SIGINT", 0));
|
|
19
|
+
process.on("SIGTERM", () => void run("SIGTERM", 0));
|
|
20
|
+
process.on("uncaughtException", (err) => {
|
|
21
|
+
console.error("[github-mcp-sketch] uncaughtException:", err);
|
|
22
|
+
void run("uncaughtException", 1);
|
|
23
|
+
});
|
|
24
|
+
process.on("unhandledRejection", (err) => {
|
|
25
|
+
console.error("[github-mcp-sketch] unhandledRejection:", err);
|
|
26
|
+
void run("unhandledRejection", 1);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/util/shutdown.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,QAA2B;IAC1D,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,MAAM,GAAG,GAAG,KAAK,EAAE,MAAc,EAAE,QAAgB,EAAE,EAAE;QACrD,IAAI,YAAY;YAAE,OAAO;QACzB,YAAY,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,GAAG,CAAC,CAAC;QAC/D,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,CAAC,EAAE,CAAC;YACZ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,GAAG,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE;QACtC,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAC;QAC7D,KAAK,GAAG,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,EAAE;QACvC,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,CAAC,CAAC;QAC9D,KAAK,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "github-mcp-sketch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP proxy in front of the GitHub MCP that returns inferred schemas instead of raw JSON, plus a query_response tool for drilling back into cached values. Built on json-schema-sketch.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"github-mcp-sketch": "dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/**/*",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/server.js",
|
|
18
|
+
"dev": "tsx src/server.ts",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"anthropic",
|
|
26
|
+
"claude",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"github",
|
|
29
|
+
"github-mcp",
|
|
30
|
+
"json-schema",
|
|
31
|
+
"token-efficiency",
|
|
32
|
+
"llm",
|
|
33
|
+
"agent"
|
|
34
|
+
],
|
|
35
|
+
"author": "Mark Adel Nawar",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/markadelnawar/github-mcp-sketch.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/markadelnawar/github-mcp-sketch/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/markadelnawar/github-mcp-sketch#readme",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
47
|
+
"json-schema-sketch": "^0.1.1",
|
|
48
|
+
"tiktoken": "^1.0.20",
|
|
49
|
+
"dotenv": "^16.4.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"tsx": "^4.19.0",
|
|
54
|
+
"typescript": "^5.7.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|