Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
228 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fixes from the page sweep1//! MCP over streamable HTTP. The server keeps no session state, so every
2//! POST is answered directly with JSON.
3
4use g1t_contracts::{Outcome, Viewer};
5use serde_json::{Value, json};
6use worker::{Method, Request, Response, Result};
7
8use crate::operations::{Op, Services};
9use crate::tools::{Gate, TOOLS, Tool};
10
11const SUPPORTED_VERSIONS: [&str; 3] = ["2025-06-18", "2025-03-26", "2024-11-05"];
12
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.13const INSTRUCTIONS: &str = "g1t is where your code, its history and the work around it live: repositories, issues, pull requests, workflows, deployments and packages. You bring your own agent; these tools are how it reads and changes what is on g1t, as the person or token it acts for. Repositories are named \"owner/name\"; issues and pull requests in one share a sequence of numbers.
The API and MCP server coordinate work: start_session, update_session (an empty body is a heartbeat), end_session, get_session, list_sessions and record_session_entries at /repos/{owner}/{name}/sessions, as the new session tool's start, update, end, get, list and record; claim_issue, release_claim, hand_off_issue and get_work at /repos/{owner}/{name}/issues/{number}/claim, /release, /handoff and /work, and list_claims at /repos/{owner}/{name}/claims, as the issue tool's claim, release, handoff, work and list_claims; and update_issue takes blocked_by. Sessions need pull_requests scopes and claims and get_work issues scopes, reads are open to anyone who can read the repository, the reference has an example of each, and the OpenAPI copy and the CLI's table of operations are regenerated.14Tools are resources, each with an `action`: search, repository, issue, pull_request, session, memory, workflow, observability, package, secret, webhook, access, team, workspace, billing, security, notifications, account, artifact. The `action` field lists each action and the fields it needs. You see only what your token's scopes allow; a refusal names the scope it needs.
Fixes from the page sweep15Find a repository: account whoami lists your workspaces; repository list or search finds one.
The API and MCP server coordinate work: start_session, update_session (an empty body is a heartbeat), end_session, get_session, list_sessions and record_session_entries at /repos/{owner}/{name}/sessions, as the new session tool's start, update, end, get, list and record; claim_issue, release_claim, hand_off_issue and get_work at /repos/{owner}/{name}/issues/{number}/claim, /release, /handoff and /work, and list_claims at /repos/{owner}/{name}/claims, as the issue tool's claim, release, handoff, work and list_claims; and update_issue takes blocked_by. Sessions need pull_requests scopes and claims and get_work issues scopes, reads are open to anyone who can read the repository, the reference has an example of each, and the OpenAPI copy and the CLI's table of operations are regenerated.16Work on an issue: issue work (everything about it: what done means, who is on it, its pull requests, what it waits on), issue claim (advisory: it tells you who else is on it), memory recall, then pull_request create with the issue's number: you get a draft with its own fork to clone and push to. Push your commits, then pull_request ready with a summary. Watch `overlaps` and `behind` on pull_request get, and its checks there: `statuses` from the repository's workflows and `required_checks`, which must pass before it merges. If one fails, read why with workflow get_run and job_logs, push a fix, and the checks run again.
Merge main into stream A: coordination meets the merge queue's decisions and the CLI. get_work gives the issue's decision records newest first and its latest integration_failure decision as last_integration_failure, a merge queue failure noted in a pull request's session is noted in the live harness sessions on it or its issue too, the CLI's coordination commands use the generated operations now that the API describes them (its pending list is empty) and show get_work's definition of done, and the session, decision and coordination events sit together in the catalog, the webhook form and the docs.17Coordinate: session start once per run of your harness (harness_session_id makes it idempotent), session update as a heartbeat, session record your prompts and tool calls, session end when done; issue release or handoff (with a note) when you stop.
18Record your session: as you work, pull_request record_session appends your prompts, reasoning and the tools you ran to the pull request's session, so reviewers see why the change is what it is; pull_request read_session reads it back. pull_request record_decision keeps a decision (why the change is made this way, or what fixed a failure) and list_decisions reads them; repository why says why a line of code is the way it is.
19Merge queue: when your pull request fails combined with the ones ahead of it, g1t says which of them it breaks with and the check that failed (on the pull request, its issue and its session, and as integration.failed). Fix it and push: once its checks pass, it rejoins the queue by itself.
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.20Memory: memory recall before you start to learn how the project builds and what it has decided; when you learn something the next agent needs, memory remember it, one short fact at a time (scope project or workspace). Never a secret.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar21Security: security code_alerts, vulnerability_alerts and secret_alerts show what to fix; fix it in your pull request, which the Code scanning and Dependency review checks judge.
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.22Production: observability overview and errors show what a project's running software did; follow a request or trace to its logs.";
Fixes from the page sweep23
24fn result(id: &Value, value: Value) -> Value {
25 json!({ "jsonrpc": "2.0", "id": id, "result": value })
26}
27
28fn error(id: &Value, code: i32, message: &str) -> Value {
29 json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } })
30}
31
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.32/// What decides the actions a caller sees: a run's scope, an
Fixes from the page sweep33/// access token's scopes, or nothing beyond the person's role.
34fn gate<'a>(services: &'a Services, viewer: &'a Viewer) -> Gate<'a> {
35 if let Some(scope) = &services.scope {
36 return Gate::Agent(scope);
37 }
38 match viewer.as_ref().and_then(|user| user.token.as_deref()) {
39 Some(access) => Gate::Token(access),
40 None => Gate::Everything,
41 }
42}
43
44/// Answers one JSON-RPC request, or `None` for a notification.
45async fn answer(services: &Services, viewer: &Viewer, request: &Value) -> Result<Option<Value>> {
46 // Notifications carry no id and get no response.
47 let Some(id) = request.get("id") else {
48 return Ok(None);
49 };
50 let params = &request["params"];
51 let answer = match request["method"].as_str().unwrap_or_default() {
52 "initialize" => {
53 let requested = params["protocolVersion"].as_str().unwrap_or_default();
54 let version = SUPPORTED_VERSIONS
55 .into_iter()
56 .find(|version| *version == requested)
57 .unwrap_or(SUPPORTED_VERSIONS[0]);
58 result(
59 id,
60 json!({
61 "protocolVersion": version,
62 "capabilities": { "tools": {} },
63 "serverInfo": { "name": "g1t", "version": "0.1.0" },
64 "instructions": INSTRUCTIONS,
65 }),
66 )
67 }
68 "ping" => result(id, json!({})),
69 "tools/list" => {
70 // A caller sees the tools, and the actions of each, that its
71 // token may use.
72 let gate = gate(services, viewer);
73 let tools: Vec<Value> = TOOLS.iter().filter_map(|tool| tool.listed(&gate)).collect();
74 result(id, json!({ "tools": tools }))
75 }
76 "tools/call" => {
77 let name = params["name"].as_str().unwrap_or_default();
78 let arguments = &params["arguments"];
79 let op = match Tool::by_name(name) {
80 Some(tool) => match crate::tools::resolve(tool, arguments) {
81 Ok(op) => op,
82 Err(problem) => {
83 return Ok(Some(result(
84 id,
85 json!({ "content": [{ "type": "text", "text": problem }], "isError": true }),
86 )));
87 }
88 },
89 // A tool per operation, as the server had before its
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.90 // resource tools. Still answered, no longer listed; never
91 // one offered over REST alone.
92 None => match Op::by_name(name).filter(|op| !crate::tools::REST_ONLY.contains(op)) {
Fixes from the page sweep93 Some(op) => op,
94 None => return Ok(Some(error(id, -32602, "Unknown tool."))),
95 },
96 };
97 let outcome = crate::audit::run(op, services, viewer, arguments).await?;
98 // A failed operation is a tool result the model can read and
99 // act on, not a protocol error.
100 let (text, failed) = match outcome {
101 // In `snake_case`, as the REST API answers; the protocol's
102 // own envelope keeps MCP's spelling.
103 Outcome::Ok(value) => (
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.104 serde_json::to_string_pretty(&g1t_kit::wire::snake_case_keeping(value, op.as_given()))?,
Fixes from the page sweep105 false,
106 ),
107 Outcome::Fail(failure) => (failure.message, true),
108 };
109 result(
110 id,
111 json!({ "content": [{ "type": "text", "text": text }], "isError": failed }),
112 )
113 }
114 method => error(id, -32601, &format!("Method not found: {method}")),
115 };
116 Ok(Some(answer))
117}
118
119/// What someone sees when they open the server's address in a browser:
120/// what this is, how to connect, and what it offers.
Merge branch 'worktree-agent-aaf03bdceac799c89'121fn card(addresses: &crate::addresses::Addresses) -> Value {
Fixes from the page sweep122 let tools: Vec<Value> = TOOLS
123 .iter()
124 .map(|tool| {
125 let actions: Vec<&crate::tools::Action> = tool.actions.iter().collect();
126 json!({
127 "name": tool.name,
128 "title": tool.title,
129 "description": tool.description,
130 "actions": tool.actions.iter().map(|action| json!({
131 "name": action.name,
132 "description": action.summary,
133 "operation": action.op.name(),
134 "scope": g1t_contracts::scopes::scope_for(action.op.name()).map(|scope| scope.as_str()),
135 })).collect::<Vec<_>>(),
136 "input_schema": tool.discriminated(&actions),
137 })
138 })
139 .collect();
140 json!({
141 "name": "g1t",
142 "description": "The g1t MCP server: issues, pull requests and sessions for agents.",
Merge branch 'worktree-agent-aaf03bdceac799c89'143 "endpoint": addresses.mcp,
Fixes from the page sweep144 "transport": "streamable-http",
145 "protocol_versions": SUPPORTED_VERSIONS,
Merge branch 'worktree-agent-aaf03bdceac799c89'146 "connect": format!("claude mcp add --transport http g1t {}", addresses.mcp),
Fixes from the page sweep147 "authorization": {
148 "required": true,
Merge branch 'worktree-agent-aaf03bdceac799c89'149 "oauth_protected_resource": addresses.protected_resource(),
Fixes from the page sweep150 "alternative": "Authorization: Bearer <g1t access token>",
151 },
152 "documentation_url": "https://docs.g1t.sh/guides/bring-your-own-agent/",
153 "instructions": INSTRUCTIONS,
154 "tools": tools,
155 })
156}
157
158pub async fn handle(
159 mut request: Request,
160 services: &Services,
161 viewer: &Viewer,
162) -> Result<Response> {
163 if request.method() != Method::Post {
164 // A client asking for a stream of server messages is told there is
165 // none. Anyone else, a person with a browser, gets a description.
166 let wants_stream = request
167 .headers()
168 .get("accept")?
169 .is_some_and(|accept| accept.contains("text/event-stream"));
170 if request.method() == Method::Get && !wants_stream {
Merge branch 'worktree-agent-aaf03bdceac799c89'171 return Response::from_json(&card(&services.addresses));
Fixes from the page sweep172 }
173 let mut response = Response::empty()?.with_status(405);
174 response.headers_mut().set("allow", "GET, POST")?;
175 return Ok(response);
176 }
177 // Calls need a signed-in user. Answering 401 with this header is what
178 // makes a client open the browser to sign in.
179 if viewer.is_none() {
180 let mut response = Response::from_json(&error(
181 &Value::Null,
182 -32001,
183 "Sign in to use the g1t MCP server.",
184 ))?
185 .with_status(401);
186 response
187 .headers_mut()
Merge branch 'worktree-agent-aaf03bdceac799c89'188 .set("www-authenticate", &services.addresses.mcp_challenge())?;
Fixes from the page sweep189 return Ok(response);
190 }
191 let Ok(body) = request.json::<Value>().await else {
192 return Ok(
193 Response::from_json(&error(&Value::Null, -32700, "Parse error"))?.with_status(400),
194 );
195 };
196 let accepted = || Ok(Response::empty()?.with_status(202));
197 match body {
198 Value::Array(batch) => {
199 let mut answers = Vec::new();
200 for request in &batch {
201 answers.extend(answer(services, viewer, request).await?);
202 }
203 if answers.is_empty() {
204 accepted()
205 } else {
206 Response::from_json(&answers)
207 }
208 }
209 single => match answer(services, viewer, &single).await? {
210 Some(answer) => Response::from_json(&answer),
211 None => accepted(),
212 },
213 }
214}
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 /// The instructions name every tool, in the order `tools/list` gives
221 /// them, so that no tool goes unmentioned.
222 #[test]
223 fn the_instructions_list_every_tool() {
224 let names: Vec<&str> = TOOLS.iter().map(|tool| tool.name).collect();
225 let listed = format!("each with an `action`: {}.", names.join(", "));
226 assert!(INSTRUCTIONS.contains(&listed), "{listed}");
227 }
228}

This file's history is long; its oldest lines are credited to the oldest commit read.