@foundation0/git 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/src/cli.ts CHANGED
@@ -1,76 +1,76 @@
1
- import { runGitMcpServer } from './server'
2
-
3
- const getArgValue = (name: string, fallback?: string): string | undefined => {
4
- const exactArg = process.argv.find((arg) => arg.startsWith(`${name}=`))
5
- if (exactArg) {
6
- const [, value] = exactArg.split('=', 2)
7
- return value
8
- }
9
-
10
- const index = process.argv.indexOf(name)
11
- if (index >= 0 && index + 1 < process.argv.length) {
12
- const next = process.argv[index + 1]
13
- if (!next.startsWith('--')) {
14
- return next
15
- }
16
- }
17
-
18
- return fallback
19
- }
20
-
21
- const hasFlag = (name: string): boolean => process.argv.includes(name)
22
-
23
- const isInsecureHttpUrl = (value: string | undefined): boolean => {
24
- if (!value) {
25
- return false
26
- }
27
-
28
- return value.trim().toLowerCase().startsWith('http://')
29
- }
30
-
31
- if (hasFlag('--help') || hasFlag('-h')) {
32
- console.log('Usage: f0-git-mcp [options]')
33
- console.log('Optional flags:')
34
- console.log(' --default-owner <owner>')
35
- console.log(' --default-repo <repo>')
36
- console.log(' --gitea-host <url>')
37
- console.log(' --server-name <name>')
38
- console.log(' --server-version <version>')
39
- console.log(' --tools-prefix <prefix>')
40
- console.log(' --allow-insecure-http')
41
- process.exit(0)
42
- }
43
-
44
- const owner = getArgValue('--default-owner')?.trim()
45
- const repo = getArgValue('--default-repo')?.trim()
46
- const host = getArgValue('--gitea-host', process.env.GITEA_HOST)?.trim()
47
- const token = process.env.GITEA_TOKEN
48
- const serverName = getArgValue('--server-name', 'f0-git-mcp')
49
- const serverVersion = getArgValue('--server-version', '1.0.0')
50
- const toolsPrefix = getArgValue('--tools-prefix') ?? process.env.MCP_TOOLS_PREFIX
51
-
52
- if (!host) {
53
- throw new Error('GITEA_HOST is required. Set process.env.GITEA_HOST or pass --gitea-host.')
54
- }
55
-
56
- if (isInsecureHttpUrl(host) && !hasFlag('--allow-insecure-http')) {
57
- throw new Error(
58
- 'Refusing to send requests to an insecure http:// Gitea host. Use https:// or pass --allow-insecure-http if you really need this for local testing.',
59
- )
60
- }
61
-
62
- void runGitMcpServer({
63
- serverName: serverName ?? undefined,
64
- serverVersion: serverVersion ?? undefined,
65
- config: {
66
- platform: 'GITEA',
67
- giteaHost: host,
68
- giteaToken: token,
69
- },
70
- ...(owner ? { defaultOwner: owner } : {}),
71
- ...(repo ? { defaultRepo: repo } : {}),
72
- toolsPrefix,
73
- }).catch((error) => {
74
- console.error('Failed to start MCP git server', error)
75
- process.exit(1)
76
- })
1
+ import { runGitMcpServer } from './server'
2
+
3
+ const getArgValue = (name: string, fallback?: string): string | undefined => {
4
+ const exactArg = process.argv.find((arg) => arg.startsWith(`${name}=`))
5
+ if (exactArg) {
6
+ const [, value] = exactArg.split('=', 2)
7
+ return value
8
+ }
9
+
10
+ const index = process.argv.indexOf(name)
11
+ if (index >= 0 && index + 1 < process.argv.length) {
12
+ const next = process.argv[index + 1]
13
+ if (!next.startsWith('--')) {
14
+ return next
15
+ }
16
+ }
17
+
18
+ return fallback
19
+ }
20
+
21
+ const hasFlag = (name: string): boolean => process.argv.includes(name)
22
+
23
+ const isInsecureHttpUrl = (value: string | undefined): boolean => {
24
+ if (!value) {
25
+ return false
26
+ }
27
+
28
+ return value.trim().toLowerCase().startsWith('http://')
29
+ }
30
+
31
+ if (hasFlag('--help') || hasFlag('-h')) {
32
+ console.log('Usage: f0-git-mcp [options]')
33
+ console.log('Optional flags:')
34
+ console.log(' --default-owner <owner>')
35
+ console.log(' --default-repo <repo>')
36
+ console.log(' --gitea-host <url>')
37
+ console.log(' --server-name <name>')
38
+ console.log(' --server-version <version>')
39
+ console.log(' --tools-prefix <prefix>')
40
+ console.log(' --allow-insecure-http')
41
+ process.exit(0)
42
+ }
43
+
44
+ const owner = getArgValue('--default-owner')?.trim()
45
+ const repo = getArgValue('--default-repo')?.trim()
46
+ const host = getArgValue('--gitea-host', process.env.GITEA_HOST)?.trim()
47
+ const token = process.env.GITEA_TOKEN
48
+ const serverName = getArgValue('--server-name', 'f0-git-mcp')
49
+ const serverVersion = getArgValue('--server-version', '1.0.0')
50
+ const toolsPrefix = getArgValue('--tools-prefix') ?? process.env.MCP_TOOLS_PREFIX
51
+
52
+ if (!host) {
53
+ throw new Error('GITEA_HOST is required. Set process.env.GITEA_HOST or pass --gitea-host.')
54
+ }
55
+
56
+ if (isInsecureHttpUrl(host) && !hasFlag('--allow-insecure-http')) {
57
+ throw new Error(
58
+ 'Refusing to send requests to an insecure http:// Gitea host. Use https:// or pass --allow-insecure-http if you really need this for local testing.',
59
+ )
60
+ }
61
+
62
+ void runGitMcpServer({
63
+ serverName: serverName ?? undefined,
64
+ serverVersion: serverVersion ?? undefined,
65
+ config: {
66
+ platform: 'GITEA',
67
+ giteaHost: host,
68
+ giteaToken: token,
69
+ },
70
+ ...(owner ? { defaultOwner: owner } : {}),
71
+ ...(repo ? { defaultRepo: repo } : {}),
72
+ toolsPrefix,
73
+ }).catch((error) => {
74
+ console.error('Failed to start MCP git server', error)
75
+ process.exit(1)
76
+ })
package/mcp/src/client.ts CHANGED
@@ -1,147 +1,147 @@
1
- import { Client } from '@modelcontextprotocol/sdk/client/index.js'
2
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
3
- import { CallToolResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
4
-
5
- type ToolResultText = {
6
- type: 'text'
7
- text: string
8
- }
9
-
10
- type ToolResult = {
11
- isError?: boolean
12
- content?: ToolResultText[]
13
- }
14
-
15
- const isRecord = (value: unknown): value is Record<string, unknown> =>
16
- typeof value === 'object' && value !== null && !Array.isArray(value)
17
-
18
- const toText = (result: ToolResult | null): string => {
19
- if (!result || !result.content || result.content.length === 0) {
20
- return ''
21
- }
22
-
23
- return result.content
24
- .map((entry) => (entry.type === 'text' ? entry.text : ''))
25
- .filter((entry) => entry.length > 0)
26
- .join('\n')
27
- }
28
-
29
- const tryParseResult = (text: string): { parsed?: unknown; text: string } => {
30
- if (!text) {
31
- return { text }
32
- }
33
-
34
- try {
35
- return {
36
- text,
37
- parsed: JSON.parse(text),
38
- }
39
- } catch {
40
- return { text }
41
- }
42
- }
43
-
44
- export interface GitMcpClientOptions {
45
- name?: string
46
- version?: string
47
- }
48
-
49
- export interface GitMcpCallArgs {
50
- args?: unknown[]
51
- options?: Record<string, unknown>
52
- [key: string]: unknown
53
- }
54
-
55
- export type GitMcpCallResponse = {
56
- isError: boolean
57
- text: string
58
- data: unknown
59
- }
60
-
61
- export class GitMcpClient {
62
- private readonly client: Client
63
- private transport: StdioClientTransport | null = null
64
-
65
- public constructor(options: GitMcpClientOptions = {}) {
66
- this.client = new Client(
67
- {
68
- name: options.name ?? 'git-mcp-client',
69
- version: options.version ?? '1.0.0',
70
- },
71
- {
72
- capabilities: {},
73
- },
74
- )
75
- }
76
-
77
- public async connect(command: string, args: string[] = []): Promise<void> {
78
- if (this.transport) {
79
- throw new Error('Client transport is already connected')
80
- }
81
-
82
- this.transport = new StdioClientTransport({
83
- command,
84
- args,
85
- })
86
-
87
- await this.client.connect(this.transport)
88
- }
89
-
90
- public async close(): Promise<void> {
91
- if (this.transport) {
92
- await this.client.close()
93
- this.transport = null
94
- }
95
- }
96
-
97
- public async listTools(): Promise<string[]> {
98
- const response = await this.client.request(
99
- {
100
- method: 'tools/list',
101
- },
102
- ListToolsResultSchema,
103
- )
104
-
105
- return response.tools.map((tool) => tool.name)
106
- }
107
-
108
- public async call(toolName: string, args: GitMcpCallArgs = {}): Promise<GitMcpCallResponse> {
109
- const response = await this.client.request(
110
- {
111
- method: 'tools/call',
112
- params: {
113
- name: toolName,
114
- arguments: args,
115
- },
116
- },
117
- CallToolResultSchema,
118
- )
119
-
120
- const text = toText(response as ToolResult)
121
- const parsed = tryParseResult(text)
122
-
123
- // Back-compat: if a server returns an { ok:false } envelope but forgets to mark MCP isError,
124
- // treat it as an error on the client side as well.
125
- const parsedIsError =
126
- isRecord(parsed.parsed) && typeof parsed.parsed.ok === 'boolean' ? parsed.parsed.ok === false : false
127
-
128
- return {
129
- isError: Boolean(response.isError) || parsedIsError,
130
- text: parsed.text,
131
- data: parsed.parsed,
132
- }
133
- }
134
-
135
- public async callByPath(
136
- path: string,
137
- methodArgs: unknown[] = [],
138
- methodOptions: Record<string, unknown> = {},
139
- ): Promise<GitMcpCallResponse> {
140
- const args = { args: methodArgs.map((value) => String(value)), options: methodOptions }
141
- return this.call(path, args)
142
- }
143
- }
144
-
145
- export const createGitMcpClient = (options: GitMcpClientOptions = {}): GitMcpClient => {
146
- return new GitMcpClient(options)
147
- }
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
3
+ import { CallToolResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
4
+
5
+ type ToolResultText = {
6
+ type: 'text'
7
+ text: string
8
+ }
9
+
10
+ type ToolResult = {
11
+ isError?: boolean
12
+ content?: ToolResultText[]
13
+ }
14
+
15
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
16
+ typeof value === 'object' && value !== null && !Array.isArray(value)
17
+
18
+ const toText = (result: ToolResult | null): string => {
19
+ if (!result || !result.content || result.content.length === 0) {
20
+ return ''
21
+ }
22
+
23
+ return result.content
24
+ .map((entry) => (entry.type === 'text' ? entry.text : ''))
25
+ .filter((entry) => entry.length > 0)
26
+ .join('\n')
27
+ }
28
+
29
+ const tryParseResult = (text: string): { parsed?: unknown; text: string } => {
30
+ if (!text) {
31
+ return { text }
32
+ }
33
+
34
+ try {
35
+ return {
36
+ text,
37
+ parsed: JSON.parse(text),
38
+ }
39
+ } catch {
40
+ return { text }
41
+ }
42
+ }
43
+
44
+ export interface GitMcpClientOptions {
45
+ name?: string
46
+ version?: string
47
+ }
48
+
49
+ export interface GitMcpCallArgs {
50
+ args?: unknown[]
51
+ options?: Record<string, unknown>
52
+ [key: string]: unknown
53
+ }
54
+
55
+ export type GitMcpCallResponse = {
56
+ isError: boolean
57
+ text: string
58
+ data: unknown
59
+ }
60
+
61
+ export class GitMcpClient {
62
+ private readonly client: Client
63
+ private transport: StdioClientTransport | null = null
64
+
65
+ public constructor(options: GitMcpClientOptions = {}) {
66
+ this.client = new Client(
67
+ {
68
+ name: options.name ?? 'git-mcp-client',
69
+ version: options.version ?? '1.0.0',
70
+ },
71
+ {
72
+ capabilities: {},
73
+ },
74
+ )
75
+ }
76
+
77
+ public async connect(command: string, args: string[] = []): Promise<void> {
78
+ if (this.transport) {
79
+ throw new Error('Client transport is already connected')
80
+ }
81
+
82
+ this.transport = new StdioClientTransport({
83
+ command,
84
+ args,
85
+ })
86
+
87
+ await this.client.connect(this.transport)
88
+ }
89
+
90
+ public async close(): Promise<void> {
91
+ if (this.transport) {
92
+ await this.client.close()
93
+ this.transport = null
94
+ }
95
+ }
96
+
97
+ public async listTools(): Promise<string[]> {
98
+ const response = await this.client.request(
99
+ {
100
+ method: 'tools/list',
101
+ },
102
+ ListToolsResultSchema,
103
+ )
104
+
105
+ return response.tools.map((tool) => tool.name)
106
+ }
107
+
108
+ public async call(toolName: string, args: GitMcpCallArgs = {}): Promise<GitMcpCallResponse> {
109
+ const response = await this.client.request(
110
+ {
111
+ method: 'tools/call',
112
+ params: {
113
+ name: toolName,
114
+ arguments: args,
115
+ },
116
+ },
117
+ CallToolResultSchema,
118
+ )
119
+
120
+ const text = toText(response as ToolResult)
121
+ const parsed = tryParseResult(text)
122
+
123
+ // Back-compat: if a server returns an { ok:false } envelope but forgets to mark MCP isError,
124
+ // treat it as an error on the client side as well.
125
+ const parsedIsError =
126
+ isRecord(parsed.parsed) && typeof parsed.parsed.ok === 'boolean' ? parsed.parsed.ok === false : false
127
+
128
+ return {
129
+ isError: Boolean(response.isError) || parsedIsError,
130
+ text: parsed.text,
131
+ data: parsed.parsed,
132
+ }
133
+ }
134
+
135
+ public async callByPath(
136
+ path: string,
137
+ methodArgs: unknown[] = [],
138
+ methodOptions: Record<string, unknown> = {},
139
+ ): Promise<GitMcpCallResponse> {
140
+ const args = { args: methodArgs.map((value) => String(value)), options: methodOptions }
141
+ return this.call(path, args)
142
+ }
143
+ }
144
+
145
+ export const createGitMcpClient = (options: GitMcpClientOptions = {}): GitMcpClient => {
146
+ return new GitMcpClient(options)
147
+ }
package/mcp/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- export {
2
- createGitMcpServer,
3
- runGitMcpServer,
4
- normalizeToolCallNameForServer,
5
- type GitMcpServerOptions,
6
- } from './server'
7
- export { GitMcpClient, createGitMcpClient, type GitMcpCallArgs, type GitMcpClientOptions, type GitMcpCallResponse } from './client'
1
+ export {
2
+ createGitMcpServer,
3
+ runGitMcpServer,
4
+ normalizeToolCallNameForServer,
5
+ type GitMcpServerOptions,
6
+ } from './server'
7
+ export { GitMcpClient, createGitMcpClient, type GitMcpCallArgs, type GitMcpClientOptions, type GitMcpCallResponse } from './client'