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.
| Topics sit in About on the files pages, under the description | 1 | //! MCP over streamable HTTP. The server keeps no session state, so every |
| 2 | //! POST is answered directly with JSON. | |
| 3 | ||
| 4 | use g1t_contracts::{Outcome, Viewer}; | |
| 5 | use serde_json::{Value, json}; | |
| 6 | use worker::{Method, Request, Response, Result}; | |
| 7 | ||
| 8 | use crate::operations::{Op, Services}; | |
| 9 | use crate::tools::{Gate, TOOLS, Tool}; | |
| 10 | ||
| 11 | const 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. | 13 | const 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. |
| 14 | Tools 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. | |
| Topics sit in About on the files pages, under the description | 15 | Find 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. | 16 | Work 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. |
| 17 | Record 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. | |
| 18 | Memory: 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 bar | 19 | Security: 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. | 20 | Production: observability overview and errors show what a project's running software did; follow a request or trace to its logs."; |
| Topics sit in About on the files pages, under the description | 21 | |
| 22 | fn result(id: &Value, value: Value) -> Value { | |
| 23 | json!({ "jsonrpc": "2.0", "id": id, "result": value }) | |
| 24 | } | |
| 25 | ||
| 26 | fn 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 |
| Topics sit in About on the files pages, under the description | 31 | /// access token's scopes, or nothing beyond the person's role. |
| 32 | fn 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. | |
| 43 | async 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 = ¶ms["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)) { | |
| Topics sit in About on the files pages, under the description | 91 | 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()))?, |
| Topics sit in About on the files pages, under the description | 103 | 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' | 119 | fn card(addresses: &crate::addresses::Addresses) -> Value { |
| Topics sit in About on the files pages, under the description | 120 | 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, |
| Topics sit in About on the files pages, under the description | 142 | "transport": "streamable-http", |
| 143 | "protocol_versions": SUPPORTED_VERSIONS, | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 144 | "connect": format!("claude mcp add --transport http g1t {}", addresses.mcp), |
| Topics sit in About on the files pages, under the description | 145 | "authorization": { |
| 146 | "required": true, | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 147 | "oauth_protected_resource": addresses.protected_resource(), |
| Topics sit in About on the files pages, under the description | 148 | "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 | ||
| 156 | pub 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)); |
| Topics sit in About on the files pages, under the description | 170 | } |
| 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())?; |
| Topics sit in About on the files pages, under the description | 187 | 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)] | |
| 215 | mod 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.