Skip to content
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.

An issue being worked on no longer offers Bring your own agent beside the pull request doing it1//! 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.
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'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.
An issue being worked on no longer offers Bring your own agent beside the pull request doing it15Find a repository: account whoami lists your workspaces; repository list or search finds one.
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'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.
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.
Merge branch 'worktree-agent-ab8033ab973f8d99a'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.";
An issue being worked on no longer offers Bring your own agent beside the pull request doing it23
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
An issue being worked on no longer offers Bring your own agent beside the pull request doing it33/// 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)) {
An issue being worked on no longer offers Bring your own agent beside the pull request doing it93 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()))?,
An issue being worked on no longer offers Bring your own agent beside the pull request doing it105 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 {
An issue being worked on no longer offers Bring your own agent beside the pull request doing it122 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,
An issue being worked on no longer offers Bring your own agent beside the pull request doing it144 "transport": "streamable-http",
145 "protocol_versions": SUPPORTED_VERSIONS,
Merge branch 'worktree-agent-aaf03bdceac799c89'146 "connect": format!("claude mcp add --transport http g1t {}", addresses.mcp),
An issue being worked on no longer offers Bring your own agent beside the pull request doing it147 "authorization": {
148 "required": true,
Merge branch 'worktree-agent-aaf03bdceac799c89'149 "oauth_protected_resource": addresses.protected_resource(),
An issue being worked on no longer offers Bring your own agent beside the pull request doing it150 "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));
An issue being worked on no longer offers Bring your own agent beside the pull request doing it172 }
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())?;
An issue being worked on no longer offers Bring your own agent beside the pull request doing it189 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.