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
226 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.

A project is an app or a library: libraries show their package and how to ship a release, not production1//! 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.
14Tools are resources, each with an `action`: search, repository, issue, pull_request, 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.
A project is an app or a library: libraries show their package and how to ship a release, not production15Find a repository: account whoami lists your workspaces; repository list or search finds one.
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.16Work on an issue: issue get (read it and the pull requests already made for 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.
17Record 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.
18Memory: 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 bar19Security: 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.20Production: observability overview and errors show what a project's running software did; follow a request or trace to its logs.";
A project is an app or a library: libraries show their package and how to ship a release, not production21
22fn result(id: &Value, value: Value) -> Value {
23 json!({ "jsonrpc": "2.0", "id": id, "result": value })
24}
25
26fn error(id: &Value, code: i32, message: &str) -> Value {
27 json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } })
28}
29
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.30/// What decides the actions a caller sees: a run's scope, an
A project is an app or a library: libraries show their package and how to ship a release, not production31/// access token's scopes, or nothing beyond the person's role.
32fn gate<'a>(services: &'a Services, viewer: &'a Viewer) -> Gate<'a> {
33 if let Some(scope) = &services.scope {
34 return Gate::Agent(scope);
35 }
36 match viewer.as_ref().and_then(|user| user.token.as_deref()) {
37 Some(access) => Gate::Token(access),
38 None => Gate::Everything,
39 }
40}
41
42/// Answers one JSON-RPC request, or `None` for a notification.
43async fn answer(services: &Services, viewer: &Viewer, request: &Value) -> Result<Option<Value>> {
44 // Notifications carry no id and get no response.
45 let Some(id) = request.get("id") else {
46 return Ok(None);
47 };
48 let params = &request["params"];
49 let answer = match request["method"].as_str().unwrap_or_default() {
50 "initialize" => {
51 let requested = params["protocolVersion"].as_str().unwrap_or_default();
52 let version = SUPPORTED_VERSIONS
53 .into_iter()
54 .find(|version| *version == requested)
55 .unwrap_or(SUPPORTED_VERSIONS[0]);
56 result(
57 id,
58 json!({
59 "protocolVersion": version,
60 "capabilities": { "tools": {} },
61 "serverInfo": { "name": "g1t", "version": "0.1.0" },
62 "instructions": INSTRUCTIONS,
63 }),
64 )
65 }
66 "ping" => result(id, json!({})),
67 "tools/list" => {
68 // A caller sees the tools, and the actions of each, that its
69 // token may use.
70 let gate = gate(services, viewer);
71 let tools: Vec<Value> = TOOLS.iter().filter_map(|tool| tool.listed(&gate)).collect();
72 result(id, json!({ "tools": tools }))
73 }
74 "tools/call" => {
75 let name = params["name"].as_str().unwrap_or_default();
76 let arguments = &params["arguments"];
77 let op = match Tool::by_name(name) {
78 Some(tool) => match crate::tools::resolve(tool, arguments) {
79 Ok(op) => op,
80 Err(problem) => {
81 return Ok(Some(result(
82 id,
83 json!({ "content": [{ "type": "text", "text": problem }], "isError": true }),
84 )));
85 }
86 },
87 // 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.88 // resource tools. Still answered, no longer listed; never
89 // one offered over REST alone.
90 None => match Op::by_name(name).filter(|op| !crate::tools::REST_ONLY.contains(op)) {
A project is an app or a library: libraries show their package and how to ship a release, not production91 Some(op) => op,
92 None => return Ok(Some(error(id, -32602, "Unknown tool."))),
93 },
94 };
95 let outcome = crate::audit::run(op, services, viewer, arguments).await?;
96 // A failed operation is a tool result the model can read and
97 // act on, not a protocol error.
98 let (text, failed) = match outcome {
99 // In `snake_case`, as the REST API answers; the protocol's
100 // own envelope keeps MCP's spelling.
101 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.102 serde_json::to_string_pretty(&g1t_kit::wire::snake_case_keeping(value, op.as_given()))?,
A project is an app or a library: libraries show their package and how to ship a release, not production103 false,
104 ),
105 Outcome::Fail(failure) => (failure.message, true),
106 };
107 result(
108 id,
109 json!({ "content": [{ "type": "text", "text": text }], "isError": failed }),
110 )
111 }
112 method => error(id, -32601, &format!("Method not found: {method}")),
113 };
114 Ok(Some(answer))
115}
116
117/// What someone sees when they open the server's address in a browser:
118/// what this is, how to connect, and what it offers.
Merge branch 'worktree-agent-aaf03bdceac799c89'119fn card(addresses: &crate::addresses::Addresses) -> Value {
A project is an app or a library: libraries show their package and how to ship a release, not production120 let tools: Vec<Value> = TOOLS
121 .iter()
122 .map(|tool| {
123 let actions: Vec<&crate::tools::Action> = tool.actions.iter().collect();
124 json!({
125 "name": tool.name,
126 "title": tool.title,
127 "description": tool.description,
128 "actions": tool.actions.iter().map(|action| json!({
129 "name": action.name,
130 "description": action.summary,
131 "operation": action.op.name(),
132 "scope": g1t_contracts::scopes::scope_for(action.op.name()).map(|scope| scope.as_str()),
133 })).collect::<Vec<_>>(),
134 "input_schema": tool.discriminated(&actions),
135 })
136 })
137 .collect();
138 json!({
139 "name": "g1t",
140 "description": "The g1t MCP server: issues, pull requests and sessions for agents.",
Merge branch 'worktree-agent-aaf03bdceac799c89'141 "endpoint": addresses.mcp,
A project is an app or a library: libraries show their package and how to ship a release, not production142 "transport": "streamable-http",
143 "protocol_versions": SUPPORTED_VERSIONS,
Merge branch 'worktree-agent-aaf03bdceac799c89'144 "connect": format!("claude mcp add --transport http g1t {}", addresses.mcp),
A project is an app or a library: libraries show their package and how to ship a release, not production145 "authorization": {
146 "required": true,
Merge branch 'worktree-agent-aaf03bdceac799c89'147 "oauth_protected_resource": addresses.protected_resource(),
A project is an app or a library: libraries show their package and how to ship a release, not production148 "alternative": "Authorization: Bearer <g1t access token>",
149 },
150 "documentation_url": "https://docs.g1t.sh/guides/bring-your-own-agent/",
151 "instructions": INSTRUCTIONS,
152 "tools": tools,
153 })
154}
155
156pub async fn handle(
157 mut request: Request,
158 services: &Services,
159 viewer: &Viewer,
160) -> Result<Response> {
161 if request.method() != Method::Post {
162 // A client asking for a stream of server messages is told there is
163 // none. Anyone else, a person with a browser, gets a description.
164 let wants_stream = request
165 .headers()
166 .get("accept")?
167 .is_some_and(|accept| accept.contains("text/event-stream"));
168 if request.method() == Method::Get && !wants_stream {
Merge branch 'worktree-agent-aaf03bdceac799c89'169 return Response::from_json(&card(&services.addresses));
A project is an app or a library: libraries show their package and how to ship a release, not production170 }
171 let mut response = Response::empty()?.with_status(405);
172 response.headers_mut().set("allow", "GET, POST")?;
173 return Ok(response);
174 }
175 // Calls need a signed-in user. Answering 401 with this header is what
176 // makes a client open the browser to sign in.
177 if viewer.is_none() {
178 let mut response = Response::from_json(&error(
179 &Value::Null,
180 -32001,
181 "Sign in to use the g1t MCP server.",
182 ))?
183 .with_status(401);
184 response
185 .headers_mut()
Merge branch 'worktree-agent-aaf03bdceac799c89'186 .set("www-authenticate", &services.addresses.mcp_challenge())?;
A project is an app or a library: libraries show their package and how to ship a release, not production187 return Ok(response);
188 }
189 let Ok(body) = request.json::<Value>().await else {
190 return Ok(
191 Response::from_json(&error(&Value::Null, -32700, "Parse error"))?.with_status(400),
192 );
193 };
194 let accepted = || Ok(Response::empty()?.with_status(202));
195 match body {
196 Value::Array(batch) => {
197 let mut answers = Vec::new();
198 for request in &batch {
199 answers.extend(answer(services, viewer, request).await?);
200 }
201 if answers.is_empty() {
202 accepted()
203 } else {
204 Response::from_json(&answers)
205 }
206 }
207 single => match answer(services, viewer, &single).await? {
208 Some(answer) => Response::from_json(&answer),
209 None => accepted(),
210 },
211 }
212}
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.213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 /// The instructions name every tool, in the order `tools/list` gives
219 /// them, so that no tool goes unmentioned.
220 #[test]
221 fn the_instructions_list_every_tool() {
222 let names: Vec<&str> = TOOLS.iter().map(|tool| tool.name).collect();
223 let listed = format!("each with an `action`: {}.", names.join(", "));
224 assert!(INSTRUCTIONS.contains(&listed), "{listed}");
225 }
226}

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