feat(routing): project_create MCP tool — gitea-first new-project pipeline (#10)
Adds the project_create tool to the routing pod that automates the
"new project" bootstrap end-to-end from claude.ai. Gitea-first
architecture: GitHub receives the repo only via push-mirror, never
via a direct GitHub API call from this server.
Four sequential calls to the gitea-mcp server (configured via
GITEA_MCP_URL):
1. create_project_from_template — Gitea repo from
template-go-{agent,web} per the 'stack' arg
2. repo_mirror_push (action=add) — push-mirror to
github.com/<GITHUB_OWNER>/<name>.git, interval 8h, sync_on_commit
3. file_write_branch — k3s/staging/<name>/namespace.yaml committed
on a staging/<name> branch in the infra repo
4. issue_create — experiment brief (hypothesis + description + stack
+ provisioning log) on the new repo, returns the issue_url
Returns gitea_url, github_url, issue_url, next_steps. The next_steps
string is the exact shell sequence the operator runs locally to
clone, scaffold via local-dev 'task new-project', and push.
Idempotency: create_project_from_template + repo_mirror_push +
file_write_branch all return JSON-RPC code -32003 (Conflict) when
their target already exists; the orchestrator swallows the conflict
and continues. Re-running on an existing repo restates the brief in
a fresh issue.
Error handling: on any non-conflict downstream failure the response
returns {reached: ["<step>",...], failed_step: "<step>"} alongside
a JSON-RPC error. No rollback — partial state stays so the operator
can resume manually.
New env vars (all optional except GITEA_MCP_URL):
GITEA_MCP_URL enables the tool
GITEA_MCP_TOKEN bearer auth for gitea-mcp
GITEA_OWNER default mathias
GITHUB_OWNER default mathiasb
INFRA_REPO default infra
GITHUB_PAT repo scope, used as mirror remote_password; never logged
Without GITEA_MCP_URL set, the tool is not registered and the
routing pod starts normally (degrades open).
internal/mcpclient/: new minimal JSON-RPC tools/call client with
bearer auth, used by project_create. Unwraps MCP's
content[0].text envelope and surfaces typed errors via mcpclient.Error.
Tests: table-driven against an httptest fake gitea-mcp covering happy
path (4-step success + correct PATCH-style arg shapes), idempotent
repo-exists, mirror failure (partial-success response with reached=
[create_repo] + failed_step=mirror), infra-commit failure (reached up
to mirror + failed_step=infra_commit), and validation errors.
Closes #10
This commit is contained in:
@@ -25,6 +25,16 @@ type RoutingConfig struct {
|
||||
RouteLocalFloor float64 // HYPERGUILD_ROUTE_LOCAL_FLOOR, default 0.90
|
||||
RouteLocalCeil float64 // HYPERGUILD_ROUTE_LOCAL_CEIL, default 0.70
|
||||
PassRateTTLSeconds int // HYPERGUILD_PASS_RATE_TTL_SECONDS, default 60
|
||||
|
||||
// project_create configuration. Empty GiteaMCPURL disables the
|
||||
// project_create tool registration so the routing pod still starts
|
||||
// in environments where it's not wired up.
|
||||
GiteaMCPURL string // GITEA_MCP_URL, e.g. http://koala:30340/mcp
|
||||
GiteaMCPToken string // GITEA_MCP_TOKEN, bearer for gitea-mcp
|
||||
GiteaOwner string // GITEA_OWNER, default mathias
|
||||
GitHubOwner string // GITHUB_OWNER, default mathiasb
|
||||
InfraRepo string // INFRA_REPO, default infra
|
||||
GitHubPAT string // GITHUB_PAT, repo scope; never logged
|
||||
}
|
||||
|
||||
func LoadRouting() (RoutingConfig, error) {
|
||||
@@ -56,6 +66,13 @@ func LoadRouting() (RoutingConfig, error) {
|
||||
}
|
||||
cfg.PassRateTTLSeconds = ttl
|
||||
|
||||
cfg.GiteaMCPURL = os.Getenv("GITEA_MCP_URL")
|
||||
cfg.GiteaMCPToken = os.Getenv("GITEA_MCP_TOKEN")
|
||||
cfg.GiteaOwner = envOr("GITEA_OWNER", "mathias")
|
||||
cfg.GitHubOwner = envOr("GITHUB_OWNER", "mathiasb")
|
||||
cfg.InfraRepo = envOr("INFRA_REPO", "infra")
|
||||
cfg.GitHubPAT = os.Getenv("GITHUB_PAT")
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user