forge-mcp-base 0.1.0a1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sanjay Davis
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.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: forge-mcp-base
3
+ Version: 0.1.0a1
4
+ Summary: Forge MCP server: JSON-RPC 2.0 over stdio, six tools, one SDK call each. A transport, nothing more.
5
+ Author-email: Sanjay Davis <psanjuknl@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: forge,mcp,sdk,autonomous-software,json-rpc
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: forge-foundation
16
+ Dynamic: license-file
17
+
18
+ # forge-mcp
19
+
20
+ The MCP server for [Forge](https://github.com/SanjayDavis/Forge) — a
21
+ deterministic, stdlib-only transport that exposes the whole Forge SDK to
22
+ any MCP client. JSON-RPC 2.0 over stdio, six tools, one SDK call each,
23
+ zero business logic.
24
+
25
+ This package is the ecosystem proof behind the SDK boundary: it is a
26
+ *separate, installable package* that consumes only the public
27
+ `forge.*` surface — no kernel internals.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ pip install forge-foundation forge-mcp-base
33
+ ```
34
+
35
+ ## Use
36
+
37
+ ```sh
38
+ forge-mcp -d myproject
39
+ ```
40
+
41
+ Or from Python:
42
+
43
+ ```sh
44
+ python -m forge_mcp.server -d myproject
45
+ ```
46
+
47
+ An MCP client (Claude, Hermes, the `mcp` SDK, any editor) connects over
48
+ stdio and gets six tools — `forge_next`, `forge_context`,
49
+ `forge_propose`, `forge_verify`, `forge_query`, `forge_replay` — each a
50
+ thin pass-through to one SDK method. The kernel decides; this
51
+ translates.
52
+
53
+ ## Reference client
54
+
55
+ A walk-through client that proves the server is a real MCP server:
56
+
57
+ ```sh
58
+ python -m forge_mcp.mcp_client -d myproject
59
+ ```
60
+
61
+ It performs the handshake, lists tools, and drives the six-tool loop
62
+ over the wire.
@@ -0,0 +1,45 @@
1
+ # forge-mcp
2
+
3
+ The MCP server for [Forge](https://github.com/SanjayDavis/Forge) — a
4
+ deterministic, stdlib-only transport that exposes the whole Forge SDK to
5
+ any MCP client. JSON-RPC 2.0 over stdio, six tools, one SDK call each,
6
+ zero business logic.
7
+
8
+ This package is the ecosystem proof behind the SDK boundary: it is a
9
+ *separate, installable package* that consumes only the public
10
+ `forge.*` surface — no kernel internals.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ pip install forge-foundation forge-mcp-base
16
+ ```
17
+
18
+ ## Use
19
+
20
+ ```sh
21
+ forge-mcp -d myproject
22
+ ```
23
+
24
+ Or from Python:
25
+
26
+ ```sh
27
+ python -m forge_mcp.server -d myproject
28
+ ```
29
+
30
+ An MCP client (Claude, Hermes, the `mcp` SDK, any editor) connects over
31
+ stdio and gets six tools — `forge_next`, `forge_context`,
32
+ `forge_propose`, `forge_verify`, `forge_query`, `forge_replay` — each a
33
+ thin pass-through to one SDK method. The kernel decides; this
34
+ translates.
35
+
36
+ ## Reference client
37
+
38
+ A walk-through client that proves the server is a real MCP server:
39
+
40
+ ```sh
41
+ python -m forge_mcp.mcp_client -d myproject
42
+ ```
43
+
44
+ It performs the handshake, lists tools, and drives the six-tool loop
45
+ over the wire.
@@ -0,0 +1,19 @@
1
+ """forge_mcp — the Forge MCP server distribution.
2
+
3
+ The Forge wire protocol as an installable package: JSON-RPC 2.0 over
4
+ stdio, six tools, one SDK call each, zero business logic. Consumes only
5
+ the public SDK (forge.ForgeClient). This is the transport that lets any
6
+ MCP client (Claude, Hermes, the `mcp` SDK, a text editor) drive a Forge
7
+ project.
8
+
9
+ forge-mcp -d PROJECT
10
+ python -m forge_mcp.server -d PROJECT
11
+
12
+ The distribution is named `forge-mcp`; the import name is `forge_mcp`.
13
+ The server is stdlib-only and never touches kernel internals.
14
+ """
15
+ from .server import (ForgeMCPServer, PROTOCOL_VERSION, SERVER_INFO, TOOLS,
16
+ main)
17
+
18
+ __all__ = ["ForgeMCPServer", "PROTOCOL_VERSION", "SERVER_INFO", "TOOLS",
19
+ "main"]
@@ -0,0 +1,141 @@
1
+ """Reference MCP client — the MCP slot, runnable.
2
+
3
+ Speaks the MCP wire protocol (JSON-RPC 2.0 over stdio) to a spawned
4
+ forge-mcp server, exactly like any MCP client would: initialize,
5
+ notifications/initialized, tools/list, then the six tools. This is the
6
+ roadmap's forge_next / forge_context / forge_propose / forge_verify /
7
+ forge_query / forge_replay surface, exercised over the wire instead of
8
+ through the SDK directly — proving the server is a real MCP server,
9
+ not a local helper.
10
+
11
+ python -m forge_mcp.mcp_client -d PROJECT [--limit N]
12
+
13
+ A walk: propose (if given a proposal file), next, context, verify,
14
+ query, replay — the six tools in one pass over whatever is ready.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import subprocess
22
+ import sys
23
+
24
+ # This client may be run from a checkout (packages/forge-mcp) or from an
25
+ # installed package; either way the directory containing the `forge_mcp`
26
+ # package is the parent of this file, so `python -m forge_mcp.server`
27
+ # resolves whether the distribution is installed or not.
28
+ PKG_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29
+
30
+
31
+ def rpc(p: subprocess.Popen, msg_id: int, method: str,
32
+ params: dict | None = None) -> dict:
33
+ """Send one JSON-RPC request over stdio, read one response line."""
34
+ payload = {"jsonrpc": "2.0", "id": msg_id, "method": method}
35
+ if params is not None:
36
+ payload["params"] = params
37
+ assert p.stdin is not None and p.stdout is not None
38
+ p.stdin.write(json.dumps(payload) + "\n")
39
+ p.stdin.flush()
40
+ line = p.stdout.readline()
41
+ if not line:
42
+ raise RuntimeError("server closed the connection")
43
+ return json.loads(line)
44
+
45
+
46
+ def main(argv: list[str] | None = None) -> int:
47
+ ap = argparse.ArgumentParser(
48
+ prog="mcp-client",
49
+ description="reference MCP client: walk the six forge tools "
50
+ "over the wire")
51
+ ap.add_argument("-d", "--dir", default=".",
52
+ help="project directory (default: .)")
53
+ ap.add_argument("--proposal", default=None,
54
+ help="proposal JSON file to commit first (optional)")
55
+ args = ap.parse_args(argv)
56
+
57
+ env = dict(os.environ)
58
+ env["PYTHONPATH"] = PKG_ROOT + os.pathsep + env.get("PYTHONPATH", "")
59
+ server = [sys.executable, "-m", "forge_mcp.server", "-d", args.dir]
60
+ p = subprocess.Popen(server,
61
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62
+ stderr=subprocess.PIPE, text=True,
63
+ encoding="utf-8", env=env)
64
+
65
+ msg_id = [0]
66
+
67
+ def call(method: str, params: dict | None = None) -> dict:
68
+ msg_id[0] += 1
69
+ return rpc(p, msg_id[0], method, params)
70
+
71
+ # ---- handshake
72
+ init = call("initialize", {"protocolVersion": "2025-11-25",
73
+ "capabilities": {},
74
+ "clientInfo": {"name": "mcp-client",
75
+ "version": "0.1.0"}})
76
+ print(f"server: {init['result']['serverInfo']['name']} "
77
+ f"{init['result']['serverInfo']['version']} — protocol "
78
+ f"{init['result']['protocolVersion']}")
79
+ # notifications/initialized: no response expected
80
+ p.stdin.write(json.dumps(
81
+ {"jsonrpc": "2.0", "method": "notifications/initialized"}) + "\n")
82
+ p.stdin.flush()
83
+
84
+ tools = call("tools/list")
85
+ names = [t["name"] for t in tools["result"]["tools"]]
86
+ print(f"tools: {len(names)} — {', '.join(names)}")
87
+
88
+ # ---- optional proposal commit
89
+ if args.proposal:
90
+ with open(args.proposal, encoding="utf-8") as f:
91
+ proposal = json.load(f)
92
+ res = call("tools/call", {"name": "forge_propose",
93
+ "arguments": {"proposal": proposal}})
94
+ body = res["result"]
95
+ if body.get("isError"):
96
+ print(f"propose: ERROR — {body['content'][0]['text']}")
97
+ else:
98
+ info = json.loads(body["content"][0]["text"])
99
+ print(f"propose: {info['committed']} events committed "
100
+ f"({len(info['tasks'])} tasks)")
101
+
102
+ # ---- the six-tool walk
103
+ nxt = call("tools/call", {"name": "forge_next", "arguments": {}})
104
+ body = nxt["result"]
105
+ if body.get("isError"):
106
+ print(f"next: ERROR — {body['content'][0]['text']}")
107
+ elif body["content"][0]["text"] == "null":
108
+ print("next: none")
109
+ else:
110
+ task = json.loads(body["content"][0]["text"])
111
+ print(f"next: {task['id']} — {task['title']} ({task['status']})")
112
+
113
+ ctx = call("tools/call", {"name": "forge_context",
114
+ "arguments": {"task_id": task["id"]}})
115
+ print(f"context: {ctx['result']['content'][0]['text']}")
116
+
117
+ ver = call("tools/call", {"name": "forge_verify",
118
+ "arguments": {"task_id": task["id"]}})
119
+ vbody = ver["result"]
120
+ if vbody.get("isError"):
121
+ print(f"verify: ERROR — {vbody['content'][0]['text']}")
122
+ else:
123
+ print(f"verify: {json.loads(vbody['content'][0]['text'])}")
124
+
125
+ q = call("tools/call", {"name": "forge_query",
126
+ "arguments": {"expr": "status == in_progress"}})
127
+ ids = json.loads(q["result"]["content"][0]["text"])
128
+ print(f"query: {len(ids)} in-progress task(s)")
129
+
130
+ rp = call("tools/call", {"name": "forge_replay", "arguments": {}})
131
+ rep = json.loads(rp["result"]["content"][0]["text"])
132
+ print(f"replay: {rep['events']} events, {rep['tasks']} tasks, "
133
+ f"{rep['done']} done")
134
+
135
+ p.stdin.close()
136
+ p.wait(timeout=10)
137
+ return 0
138
+
139
+
140
+ if __name__ == "__main__":
141
+ sys.exit(main())
@@ -0,0 +1,258 @@
1
+ """M5 — the MCP server (SPEC Appendix A: MCP interfaces may evolve
2
+ freely). Ships as the `forge-mcp` distribution; import name `forge_mcp`.
3
+
4
+ The Forge wire protocol in one file, stdlib-only: JSON-RPC 2.0 over
5
+ stdio, one JSON object per line (the MCP stdio framing), six tools —
6
+ forge_next, forge_context, forge_propose, forge_verify, forge_query,
7
+ forge_replay — each a thin pass-through to one SDK method. No business
8
+ logic: the server is a transport, and that is the whole point. The
9
+ kernel decides; the SDK exposes; this translates JSON-RPC to SDK calls
10
+ and back.
11
+
12
+ forge-mcp -d PROJECT
13
+ python -m forge_mcp.server -d PROJECT
14
+
15
+ Clients: any MCP client (Claude, Hermes, `mcp` SDK) speaks to it over
16
+ stdio. This reference server implements the protocol itself — no `mcp`
17
+ SDK dependency — so the plugin stays as boring as the roadmap promises.
18
+ It consumes ONLY the public SDK (forge.ForgeClient). No kernel
19
+ internals, no graph, no replay of its own: everything the tools return
20
+ comes from SDK methods.
21
+
22
+ Tool semantics (one SDK call each, nothing more):
23
+ forge_next -> ForgeClient.next()
24
+ forge_context -> ForgeClient.context(task_id) (the YAML package)
25
+ forge_propose -> ForgeClient.propose(proposal)
26
+ forge_verify -> ForgeClient.verify(task_id)
27
+ forge_query -> ForgeClient.query(expr)
28
+ forge_replay -> ForgeClient.replay()
29
+
30
+ Errors: a tool that raises GraphError / ProposalError comes back as an
31
+ MCP tool error (isError result, message = the kernel's), never as a
32
+ JSON-RPC fault — the protocol layer itself only faults on protocol
33
+ violations (parse errors, unknown methods, missing arguments).
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import json
39
+ import sys
40
+ from typing import Any, Callable
41
+
42
+ from forge import ForgeClient, GraphError, ProposalError
43
+
44
+ # The MCP protocol version this server speaks. Clients negotiate at
45
+ # initialize; we echo a known version back (2024-11-05 and later are
46
+ # wire-compatible for tools-only servers), defaulting to the newest.
47
+ PROTOCOL_VERSION = "2025-11-25"
48
+ KNOWN_VERSIONS = ("2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25")
49
+
50
+ SERVER_INFO = {"name": "forge", "version": "0.1.0-alpha"}
51
+
52
+ JSON_RPC = "2.0"
53
+ # JSON-RPC error codes (the MCP spec reuses these verbatim).
54
+ PARSE_ERROR = -32700
55
+ INVALID_REQUEST = -32600
56
+ METHOD_NOT_FOUND = -32601
57
+ INVALID_PARAMS = -32602
58
+ INTERNAL_ERROR = -32603
59
+
60
+
61
+ class MethodNotFound(Exception):
62
+ """A JSON-RPC-level fault: the client called a method this server
63
+ does not implement. Distinct from a tool that failed (isError)."""
64
+
65
+
66
+ class MissingArgument(Exception):
67
+ """A tools/call request omitted a required inputSchema argument.
68
+ A JSON-RPC fault (-32602), distinct from KeyErrors a tool handler
69
+ may raise internally (those are tool failures, isError)."""
70
+
71
+
72
+ def _json_text(value: Any) -> str:
73
+ """Tool results are JSON text; the context package is already YAML."""
74
+ return json.dumps(value, ensure_ascii=False, indent=2)
75
+
76
+
77
+ def _tool(name: str, description: str, props: dict[str, Any],
78
+ required: list[str], fn: Callable[[ForgeClient, dict], str]) -> dict:
79
+ return {"name": name, "description": description,
80
+ "inputSchema": {"type": "object", "properties": props,
81
+ "required": required},
82
+ "handler": fn}
83
+
84
+
85
+ TOOLS: list[dict[str, Any]] = [
86
+ _tool(
87
+ "forge_next",
88
+ "The single next work item: highest priority, then creation "
89
+ "order. Returns a task snapshot, or null when nothing is ready.",
90
+ {}, [],
91
+ lambda c, a: _json_text(c.next())),
92
+ _tool(
93
+ "forge_context",
94
+ "The Context Contract package for TASK (Task / Description / "
95
+ "Acceptance / Dependencies / Knowledge / Relevant Files / "
96
+ "Evidence / Constraints), as the same YAML every client reads.",
97
+ {"task_id": {"type": "string"}}, ["task_id"],
98
+ lambda c, a: c.context(a["task_id"])),
99
+ _tool(
100
+ "forge_propose",
101
+ "Commit a proposal atomically (SPEC §9): envelope validated, "
102
+ "then the kernel validates and applies — whole or nothing. "
103
+ "Takes the full proposal object (proposal_id, confidence, "
104
+ "events) and returns the commit result.",
105
+ {"proposal": {"type": "object"}}, ["proposal"],
106
+ lambda c, a: _json_text(c.propose(a["proposal"]))),
107
+ _tool(
108
+ "forge_verify",
109
+ "Run the verifier gate (I6) on TASK: only started tasks with "
110
+ "all dependencies done can pass; the kernel decides done.",
111
+ {"task_id": {"type": "string"}}, ["task_id"],
112
+ lambda c, a: _json_text(c.verify(a["task_id"]))),
113
+ _tool(
114
+ "forge_query",
115
+ "Run a query over the task graph (safe expression subset: "
116
+ "status, priority, evidence_count, files, depends_on, and/or/"
117
+ "not, comparison operators). Returns matching task ids.",
118
+ {"expr": {"type": "string"}}, ["expr"],
119
+ lambda c, a: _json_text(c.query(a["expr"]))),
120
+ _tool(
121
+ "forge_replay",
122
+ "Replay the event log and report the project state: event "
123
+ "count, task count, done count.",
124
+ {}, [],
125
+ lambda c, a: _json_text(c.replay())),
126
+ ]
127
+
128
+
129
+ class ForgeMCPServer:
130
+ """JSON-RPC 2.0 over stdio, one JSON object per line. Every request
131
+ maps to exactly one SDK call; every result is a kernel verdict."""
132
+
133
+ def __init__(self, directory: str = ".") -> None:
134
+ self.client = ForgeClient(directory)
135
+ self.tools = {t["name"]: t for t in TOOLS}
136
+
137
+ # ------------------------------------------------------------------ wire
138
+ def handle_line(self, line: str) -> str | None:
139
+ """One incoming JSON-RPC message -> one response line (or None
140
+ for notifications, which never get a response)."""
141
+ try:
142
+ msg = json.loads(line)
143
+ except json.JSONDecodeError:
144
+ return self._error(None, PARSE_ERROR, "parse error")
145
+ if not isinstance(msg, dict) or msg.get("jsonrpc") != JSON_RPC:
146
+ return self._error(msg.get("id") if isinstance(msg, dict) else None,
147
+ INVALID_REQUEST, "invalid request")
148
+ method = msg.get("method")
149
+ if not isinstance(method, str):
150
+ return self._error(msg.get("id"), INVALID_REQUEST, "invalid request")
151
+ params = msg.get("params") or {}
152
+ if not isinstance(params, dict):
153
+ return self._error(msg.get("id"), INVALID_PARAMS,
154
+ "params must be an object")
155
+ is_notification = "id" not in msg
156
+
157
+ try:
158
+ result = self._dispatch(method, params)
159
+ except MethodNotFound:
160
+ return self._error(msg.get("id"), METHOD_NOT_FOUND,
161
+ f"method not found: {method}")
162
+ except GraphError as exc:
163
+ return self._tool_error(msg.get("id"), method, str(exc))
164
+ except ProposalError as exc:
165
+ return self._tool_error(msg.get("id"), method, str(exc))
166
+ except MissingArgument as exc:
167
+ return self._error(msg.get("id"), INVALID_PARAMS,
168
+ f"missing required argument: {exc}")
169
+ except Exception as exc: # tool failure, not a protocol fault
170
+ return self._tool_error(msg.get("id"), method, str(exc))
171
+
172
+ if is_notification:
173
+ return None
174
+ return self._result(msg["id"], result)
175
+
176
+ def _dispatch(self, method: str, params: dict) -> Any:
177
+ if method == "initialize":
178
+ requested = params.get("protocolVersion")
179
+ return {"protocolVersion": requested if requested in KNOWN_VERSIONS
180
+ else PROTOCOL_VERSION,
181
+ "capabilities": {"tools": {}},
182
+ "serverInfo": SERVER_INFO}
183
+ if method == "ping":
184
+ return {}
185
+ if method == "tools/list":
186
+ return {"tools": [{"name": t["name"],
187
+ "description": t["description"],
188
+ "inputSchema": t["inputSchema"]}
189
+ for t in TOOLS]}
190
+ if method == "tools/call":
191
+ name = params.get("name")
192
+ tool = self.tools.get(name)
193
+ if tool is None:
194
+ raise GraphError(f"unknown tool: {name}")
195
+ args = params.get("arguments") or {}
196
+ if not isinstance(args, dict):
197
+ raise MissingArgument("arguments (must be an object)")
198
+ for req in tool["inputSchema"].get("required", []):
199
+ if req not in args:
200
+ raise MissingArgument(req)
201
+ text = tool["handler"](self.client, args)
202
+ return {"content": [{"type": "text", "text": text}],
203
+ "isError": False}
204
+ # notifications we accept silently
205
+ if method in ("notifications/initialized", "notifications/cancelled"):
206
+ return None
207
+ raise MethodNotFound(method)
208
+
209
+ # ------------------------------------------------------------------ json
210
+ @staticmethod
211
+ def _result(msg_id: Any, result: Any) -> str:
212
+ return json.dumps({"jsonrpc": JSON_RPC, "id": msg_id, "result": result},
213
+ ensure_ascii=False)
214
+
215
+ @staticmethod
216
+ def _error(msg_id: Any, code: int, message: str) -> str:
217
+ return json.dumps({"jsonrpc": JSON_RPC, "id": msg_id,
218
+ "error": {"code": code, "message": message}},
219
+ ensure_ascii=False)
220
+
221
+ @staticmethod
222
+ def _tool_error(msg_id: Any, method: str, message: str) -> str:
223
+ """A tool that failed is a tool result (isError), not a JSON-RPC
224
+ fault — the transport stays healthy; the kernel's verdict is
225
+ the message."""
226
+ return json.dumps({"jsonrpc": JSON_RPC, "id": msg_id,
227
+ "result": {"content": [{"type": "text",
228
+ "text": message}],
229
+ "isError": True}},
230
+ ensure_ascii=False)
231
+
232
+ # ------------------------------------------------------------------ loop
233
+ def run(self) -> int:
234
+ """Serve until EOF. One JSON object per line, per the MCP stdio
235
+ framing. Errors go to stderr; stdout carries protocol only."""
236
+ for line in sys.stdin:
237
+ line = line.strip()
238
+ if not line:
239
+ continue
240
+ response = self.handle_line(line)
241
+ if response is not None:
242
+ print(response, flush=True)
243
+ return 0
244
+
245
+
246
+ def main(argv: list[str] | None = None) -> int:
247
+ ap = argparse.ArgumentParser(
248
+ prog="forge-mcp",
249
+ description="Forge MCP server: JSON-RPC 2.0 over stdio, six "
250
+ "tools, one SDK call each")
251
+ ap.add_argument("-d", "--dir", default=".",
252
+ help="project directory (default: .)")
253
+ args = ap.parse_args(argv)
254
+ return ForgeMCPServer(args.dir).run()
255
+
256
+
257
+ if __name__ == "__main__":
258
+ sys.exit(main())
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: forge-mcp-base
3
+ Version: 0.1.0a1
4
+ Summary: Forge MCP server: JSON-RPC 2.0 over stdio, six tools, one SDK call each. A transport, nothing more.
5
+ Author-email: Sanjay Davis <psanjuknl@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: forge,mcp,sdk,autonomous-software,json-rpc
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: forge-foundation
16
+ Dynamic: license-file
17
+
18
+ # forge-mcp
19
+
20
+ The MCP server for [Forge](https://github.com/SanjayDavis/Forge) — a
21
+ deterministic, stdlib-only transport that exposes the whole Forge SDK to
22
+ any MCP client. JSON-RPC 2.0 over stdio, six tools, one SDK call each,
23
+ zero business logic.
24
+
25
+ This package is the ecosystem proof behind the SDK boundary: it is a
26
+ *separate, installable package* that consumes only the public
27
+ `forge.*` surface — no kernel internals.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ pip install forge-foundation forge-mcp-base
33
+ ```
34
+
35
+ ## Use
36
+
37
+ ```sh
38
+ forge-mcp -d myproject
39
+ ```
40
+
41
+ Or from Python:
42
+
43
+ ```sh
44
+ python -m forge_mcp.server -d myproject
45
+ ```
46
+
47
+ An MCP client (Claude, Hermes, the `mcp` SDK, any editor) connects over
48
+ stdio and gets six tools — `forge_next`, `forge_context`,
49
+ `forge_propose`, `forge_verify`, `forge_query`, `forge_replay` — each a
50
+ thin pass-through to one SDK method. The kernel decides; this
51
+ translates.
52
+
53
+ ## Reference client
54
+
55
+ A walk-through client that proves the server is a real MCP server:
56
+
57
+ ```sh
58
+ python -m forge_mcp.mcp_client -d myproject
59
+ ```
60
+
61
+ It performs the handshake, lists tools, and drives the six-tool loop
62
+ over the wire.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ forge_mcp/__init__.py
5
+ forge_mcp/mcp_client.py
6
+ forge_mcp/server.py
7
+ forge_mcp_base.egg-info/PKG-INFO
8
+ forge_mcp_base.egg-info/SOURCES.txt
9
+ forge_mcp_base.egg-info/dependency_links.txt
10
+ forge_mcp_base.egg-info/entry_points.txt
11
+ forge_mcp_base.egg-info/requires.txt
12
+ forge_mcp_base.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ forge-mcp = forge_mcp.server:main
@@ -0,0 +1 @@
1
+ forge-foundation
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "forge-mcp-base"
7
+ version = "0.1.0a1"
8
+ description = "Forge MCP server: JSON-RPC 2.0 over stdio, six tools, one SDK call each. A transport, nothing more."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "Sanjay Davis", email = "psanjuknl@gmail.com" }]
13
+ keywords = ["forge", "mcp", "sdk", "autonomous-software", "json-rpc"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ # Depends on the forge-foundation distribution (PyPI name; the import name
21
+ # is "forge"). NEVER depend on plain "forge" — that name is owned by an
22
+ # unrelated Django project on PyPI.
23
+ dependencies = ["forge-foundation"]
24
+
25
+ [project.scripts]
26
+ # The MCP server is a standalone process, not a `forge` subcommand — it
27
+ # gets its own console script, like any MCP server binary.
28
+ forge-mcp = "forge_mcp.server:main"
29
+
30
+ [tool.setuptools]
31
+ packages = ["forge_mcp"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+